##// END OF EJS Templates
ui: provide a mechanism to track and log blocked time...
Simon Farnsworth -
r30976:e92daf15 default
parent child Browse files
Show More
@@ -1,889 +1,892
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 atexit
10 import atexit
11 import difflib
11 import difflib
12 import errno
12 import errno
13 import getopt
13 import getopt
14 import os
14 import os
15 import pdb
15 import pdb
16 import re
16 import re
17 import signal
17 import signal
18 import sys
18 import sys
19 import time
19 import time
20 import traceback
20 import traceback
21
21
22
22
23 from .i18n import _
23 from .i18n import _
24
24
25 from . import (
25 from . import (
26 cmdutil,
26 cmdutil,
27 color,
27 color,
28 commands,
28 commands,
29 debugcommands,
29 debugcommands,
30 demandimport,
30 demandimport,
31 encoding,
31 encoding,
32 error,
32 error,
33 extensions,
33 extensions,
34 fancyopts,
34 fancyopts,
35 fileset,
35 fileset,
36 hg,
36 hg,
37 hook,
37 hook,
38 profiling,
38 profiling,
39 pycompat,
39 pycompat,
40 revset,
40 revset,
41 scmutil,
41 scmutil,
42 templatefilters,
42 templatefilters,
43 templatekw,
43 templatekw,
44 templater,
44 templater,
45 ui as uimod,
45 ui as uimod,
46 util,
46 util,
47 )
47 )
48
48
49 class request(object):
49 class request(object):
50 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
50 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
51 ferr=None):
51 ferr=None):
52 self.args = args
52 self.args = args
53 self.ui = ui
53 self.ui = ui
54 self.repo = repo
54 self.repo = repo
55
55
56 # input/output/error streams
56 # input/output/error streams
57 self.fin = fin
57 self.fin = fin
58 self.fout = fout
58 self.fout = fout
59 self.ferr = ferr
59 self.ferr = ferr
60
60
61 def run():
61 def run():
62 "run the command in sys.argv"
62 "run the command in sys.argv"
63 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
63 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
64
64
65 def _getsimilar(symbols, value):
65 def _getsimilar(symbols, value):
66 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
66 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
67 # The cutoff for similarity here is pretty arbitrary. It should
67 # The cutoff for similarity here is pretty arbitrary. It should
68 # probably be investigated and tweaked.
68 # probably be investigated and tweaked.
69 return [s for s in symbols if sim(s) > 0.6]
69 return [s for s in symbols if sim(s) > 0.6]
70
70
71 def _reportsimilar(write, similar):
71 def _reportsimilar(write, similar):
72 if len(similar) == 1:
72 if len(similar) == 1:
73 write(_("(did you mean %s?)\n") % similar[0])
73 write(_("(did you mean %s?)\n") % similar[0])
74 elif similar:
74 elif similar:
75 ss = ", ".join(sorted(similar))
75 ss = ", ".join(sorted(similar))
76 write(_("(did you mean one of %s?)\n") % ss)
76 write(_("(did you mean one of %s?)\n") % ss)
77
77
78 def _formatparse(write, inst):
78 def _formatparse(write, inst):
79 similar = []
79 similar = []
80 if isinstance(inst, error.UnknownIdentifier):
80 if isinstance(inst, error.UnknownIdentifier):
81 # make sure to check fileset first, as revset can invoke fileset
81 # make sure to check fileset first, as revset can invoke fileset
82 similar = _getsimilar(inst.symbols, inst.function)
82 similar = _getsimilar(inst.symbols, inst.function)
83 if len(inst.args) > 1:
83 if len(inst.args) > 1:
84 write(_("hg: parse error at %s: %s\n") %
84 write(_("hg: parse error at %s: %s\n") %
85 (inst.args[1], inst.args[0]))
85 (inst.args[1], inst.args[0]))
86 if (inst.args[0][0] == ' '):
86 if (inst.args[0][0] == ' '):
87 write(_("unexpected leading whitespace\n"))
87 write(_("unexpected leading whitespace\n"))
88 else:
88 else:
89 write(_("hg: parse error: %s\n") % inst.args[0])
89 write(_("hg: parse error: %s\n") % inst.args[0])
90 _reportsimilar(write, similar)
90 _reportsimilar(write, similar)
91 if inst.hint:
91 if inst.hint:
92 write(_("(%s)\n") % inst.hint)
92 write(_("(%s)\n") % inst.hint)
93
93
94 def dispatch(req):
94 def dispatch(req):
95 "run the command specified in req.args"
95 "run the command specified in req.args"
96 if req.ferr:
96 if req.ferr:
97 ferr = req.ferr
97 ferr = req.ferr
98 elif req.ui:
98 elif req.ui:
99 ferr = req.ui.ferr
99 ferr = req.ui.ferr
100 else:
100 else:
101 ferr = util.stderr
101 ferr = util.stderr
102
102
103 try:
103 try:
104 if not req.ui:
104 if not req.ui:
105 req.ui = uimod.ui.load()
105 req.ui = uimod.ui.load()
106 if '--traceback' in req.args:
106 if '--traceback' in req.args:
107 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
107 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
108
108
109 # set ui streams from the request
109 # set ui streams from the request
110 if req.fin:
110 if req.fin:
111 req.ui.fin = req.fin
111 req.ui.fin = req.fin
112 if req.fout:
112 if req.fout:
113 req.ui.fout = req.fout
113 req.ui.fout = req.fout
114 if req.ferr:
114 if req.ferr:
115 req.ui.ferr = req.ferr
115 req.ui.ferr = req.ferr
116 except error.Abort as inst:
116 except error.Abort as inst:
117 ferr.write(_("abort: %s\n") % inst)
117 ferr.write(_("abort: %s\n") % inst)
118 if inst.hint:
118 if inst.hint:
119 ferr.write(_("(%s)\n") % inst.hint)
119 ferr.write(_("(%s)\n") % inst.hint)
120 return -1
120 return -1
121 except error.ParseError as inst:
121 except error.ParseError as inst:
122 _formatparse(ferr.write, inst)
122 _formatparse(ferr.write, inst)
123 return -1
123 return -1
124
124
125 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
125 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
126 starttime = util.timer()
126 starttime = util.timer()
127 ret = None
127 ret = None
128 try:
128 try:
129 ret = _runcatch(req)
129 ret = _runcatch(req)
130 except KeyboardInterrupt:
130 except KeyboardInterrupt:
131 try:
131 try:
132 req.ui.warn(_("interrupted!\n"))
132 req.ui.warn(_("interrupted!\n"))
133 except IOError as inst:
133 except IOError as inst:
134 if inst.errno != errno.EPIPE:
134 if inst.errno != errno.EPIPE:
135 raise
135 raise
136 ret = -1
136 ret = -1
137 finally:
137 finally:
138 duration = util.timer() - starttime
138 duration = util.timer() - starttime
139 req.ui.flush()
139 req.ui.flush()
140 if req.ui.logblockedtimes:
141 req.ui._blockedtimes['command_duration'] = duration * 1000
142 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
140 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
143 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
141 msg, ret or 0, duration)
144 msg, ret or 0, duration)
142 return ret
145 return ret
143
146
144 def _runcatch(req):
147 def _runcatch(req):
145 def catchterm(*args):
148 def catchterm(*args):
146 raise error.SignalInterrupt
149 raise error.SignalInterrupt
147
150
148 ui = req.ui
151 ui = req.ui
149 try:
152 try:
150 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
153 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
151 num = getattr(signal, name, None)
154 num = getattr(signal, name, None)
152 if num:
155 if num:
153 signal.signal(num, catchterm)
156 signal.signal(num, catchterm)
154 except ValueError:
157 except ValueError:
155 pass # happens if called in a thread
158 pass # happens if called in a thread
156
159
157 def _runcatchfunc():
160 def _runcatchfunc():
158 try:
161 try:
159 debugger = 'pdb'
162 debugger = 'pdb'
160 debugtrace = {
163 debugtrace = {
161 'pdb' : pdb.set_trace
164 'pdb' : pdb.set_trace
162 }
165 }
163 debugmortem = {
166 debugmortem = {
164 'pdb' : pdb.post_mortem
167 'pdb' : pdb.post_mortem
165 }
168 }
166
169
167 # read --config before doing anything else
170 # read --config before doing anything else
168 # (e.g. to change trust settings for reading .hg/hgrc)
171 # (e.g. to change trust settings for reading .hg/hgrc)
169 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
172 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
170
173
171 if req.repo:
174 if req.repo:
172 # copy configs that were passed on the cmdline (--config) to
175 # copy configs that were passed on the cmdline (--config) to
173 # the repo ui
176 # the repo ui
174 for sec, name, val in cfgs:
177 for sec, name, val in cfgs:
175 req.repo.ui.setconfig(sec, name, val, source='--config')
178 req.repo.ui.setconfig(sec, name, val, source='--config')
176
179
177 # developer config: ui.debugger
180 # developer config: ui.debugger
178 debugger = ui.config("ui", "debugger")
181 debugger = ui.config("ui", "debugger")
179 debugmod = pdb
182 debugmod = pdb
180 if not debugger or ui.plain():
183 if not debugger or ui.plain():
181 # if we are in HGPLAIN mode, then disable custom debugging
184 # if we are in HGPLAIN mode, then disable custom debugging
182 debugger = 'pdb'
185 debugger = 'pdb'
183 elif '--debugger' in req.args:
186 elif '--debugger' in req.args:
184 # This import can be slow for fancy debuggers, so only
187 # This import can be slow for fancy debuggers, so only
185 # do it when absolutely necessary, i.e. when actual
188 # do it when absolutely necessary, i.e. when actual
186 # debugging has been requested
189 # debugging has been requested
187 with demandimport.deactivated():
190 with demandimport.deactivated():
188 try:
191 try:
189 debugmod = __import__(debugger)
192 debugmod = __import__(debugger)
190 except ImportError:
193 except ImportError:
191 pass # Leave debugmod = pdb
194 pass # Leave debugmod = pdb
192
195
193 debugtrace[debugger] = debugmod.set_trace
196 debugtrace[debugger] = debugmod.set_trace
194 debugmortem[debugger] = debugmod.post_mortem
197 debugmortem[debugger] = debugmod.post_mortem
195
198
196 # enter the debugger before command execution
199 # enter the debugger before command execution
197 if '--debugger' in req.args:
200 if '--debugger' in req.args:
198 ui.warn(_("entering debugger - "
201 ui.warn(_("entering debugger - "
199 "type c to continue starting hg or h for help\n"))
202 "type c to continue starting hg or h for help\n"))
200
203
201 if (debugger != 'pdb' and
204 if (debugger != 'pdb' and
202 debugtrace[debugger] == debugtrace['pdb']):
205 debugtrace[debugger] == debugtrace['pdb']):
203 ui.warn(_("%s debugger specified "
206 ui.warn(_("%s debugger specified "
204 "but its module was not found\n") % debugger)
207 "but its module was not found\n") % debugger)
205 with demandimport.deactivated():
208 with demandimport.deactivated():
206 debugtrace[debugger]()
209 debugtrace[debugger]()
207 try:
210 try:
208 return _dispatch(req)
211 return _dispatch(req)
209 finally:
212 finally:
210 ui.flush()
213 ui.flush()
211 except: # re-raises
214 except: # re-raises
212 # enter the debugger when we hit an exception
215 # enter the debugger when we hit an exception
213 if '--debugger' in req.args:
216 if '--debugger' in req.args:
214 traceback.print_exc()
217 traceback.print_exc()
215 debugmortem[debugger](sys.exc_info()[2])
218 debugmortem[debugger](sys.exc_info()[2])
216 ui.traceback()
219 ui.traceback()
217 raise
220 raise
218
221
219 return callcatch(ui, _runcatchfunc)
222 return callcatch(ui, _runcatchfunc)
220
223
221 def callcatch(ui, func):
224 def callcatch(ui, func):
222 """like scmutil.callcatch but handles more high-level exceptions about
225 """like scmutil.callcatch but handles more high-level exceptions about
223 config parsing and commands. besides, use handlecommandexception to handle
226 config parsing and commands. besides, use handlecommandexception to handle
224 uncaught exceptions.
227 uncaught exceptions.
225 """
228 """
226 try:
229 try:
227 return scmutil.callcatch(ui, func)
230 return scmutil.callcatch(ui, func)
228 except error.AmbiguousCommand as inst:
231 except error.AmbiguousCommand as inst:
229 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
232 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
230 (inst.args[0], " ".join(inst.args[1])))
233 (inst.args[0], " ".join(inst.args[1])))
231 except error.CommandError as inst:
234 except error.CommandError as inst:
232 if inst.args[0]:
235 if inst.args[0]:
233 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
236 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
234 commands.help_(ui, inst.args[0], full=False, command=True)
237 commands.help_(ui, inst.args[0], full=False, command=True)
235 else:
238 else:
236 ui.warn(_("hg: %s\n") % inst.args[1])
239 ui.warn(_("hg: %s\n") % inst.args[1])
237 commands.help_(ui, 'shortlist')
240 commands.help_(ui, 'shortlist')
238 except error.ParseError as inst:
241 except error.ParseError as inst:
239 _formatparse(ui.warn, inst)
242 _formatparse(ui.warn, inst)
240 return -1
243 return -1
241 except error.UnknownCommand as inst:
244 except error.UnknownCommand as inst:
242 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
245 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
243 try:
246 try:
244 # check if the command is in a disabled extension
247 # check if the command is in a disabled extension
245 # (but don't check for extensions themselves)
248 # (but don't check for extensions themselves)
246 commands.help_(ui, inst.args[0], unknowncmd=True)
249 commands.help_(ui, inst.args[0], unknowncmd=True)
247 except (error.UnknownCommand, error.Abort):
250 except (error.UnknownCommand, error.Abort):
248 suggested = False
251 suggested = False
249 if len(inst.args) == 2:
252 if len(inst.args) == 2:
250 sim = _getsimilar(inst.args[1], inst.args[0])
253 sim = _getsimilar(inst.args[1], inst.args[0])
251 if sim:
254 if sim:
252 _reportsimilar(ui.warn, sim)
255 _reportsimilar(ui.warn, sim)
253 suggested = True
256 suggested = True
254 if not suggested:
257 if not suggested:
255 commands.help_(ui, 'shortlist')
258 commands.help_(ui, 'shortlist')
256 except IOError:
259 except IOError:
257 raise
260 raise
258 except KeyboardInterrupt:
261 except KeyboardInterrupt:
259 raise
262 raise
260 except: # probably re-raises
263 except: # probably re-raises
261 if not handlecommandexception(ui):
264 if not handlecommandexception(ui):
262 raise
265 raise
263
266
264 return -1
267 return -1
265
268
266 def aliasargs(fn, givenargs):
269 def aliasargs(fn, givenargs):
267 args = getattr(fn, 'args', [])
270 args = getattr(fn, 'args', [])
268 if args:
271 if args:
269 cmd = ' '.join(map(util.shellquote, args))
272 cmd = ' '.join(map(util.shellquote, args))
270
273
271 nums = []
274 nums = []
272 def replacer(m):
275 def replacer(m):
273 num = int(m.group(1)) - 1
276 num = int(m.group(1)) - 1
274 nums.append(num)
277 nums.append(num)
275 if num < len(givenargs):
278 if num < len(givenargs):
276 return givenargs[num]
279 return givenargs[num]
277 raise error.Abort(_('too few arguments for command alias'))
280 raise error.Abort(_('too few arguments for command alias'))
278 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
281 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
279 givenargs = [x for i, x in enumerate(givenargs)
282 givenargs = [x for i, x in enumerate(givenargs)
280 if i not in nums]
283 if i not in nums]
281 args = pycompat.shlexsplit(cmd)
284 args = pycompat.shlexsplit(cmd)
282 return args + givenargs
285 return args + givenargs
283
286
284 def aliasinterpolate(name, args, cmd):
287 def aliasinterpolate(name, args, cmd):
285 '''interpolate args into cmd for shell aliases
288 '''interpolate args into cmd for shell aliases
286
289
287 This also handles $0, $@ and "$@".
290 This also handles $0, $@ and "$@".
288 '''
291 '''
289 # util.interpolate can't deal with "$@" (with quotes) because it's only
292 # util.interpolate can't deal with "$@" (with quotes) because it's only
290 # built to match prefix + patterns.
293 # built to match prefix + patterns.
291 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
294 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
292 replacemap['$0'] = name
295 replacemap['$0'] = name
293 replacemap['$$'] = '$'
296 replacemap['$$'] = '$'
294 replacemap['$@'] = ' '.join(args)
297 replacemap['$@'] = ' '.join(args)
295 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
298 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
296 # parameters, separated out into words. Emulate the same behavior here by
299 # parameters, separated out into words. Emulate the same behavior here by
297 # quoting the arguments individually. POSIX shells will then typically
300 # quoting the arguments individually. POSIX shells will then typically
298 # tokenize each argument into exactly one word.
301 # tokenize each argument into exactly one word.
299 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
302 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
300 # escape '\$' for regex
303 # escape '\$' for regex
301 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
304 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
302 r = re.compile(regex)
305 r = re.compile(regex)
303 return r.sub(lambda x: replacemap[x.group()], cmd)
306 return r.sub(lambda x: replacemap[x.group()], cmd)
304
307
305 class cmdalias(object):
308 class cmdalias(object):
306 def __init__(self, name, definition, cmdtable, source):
309 def __init__(self, name, definition, cmdtable, source):
307 self.name = self.cmd = name
310 self.name = self.cmd = name
308 self.cmdname = ''
311 self.cmdname = ''
309 self.definition = definition
312 self.definition = definition
310 self.fn = None
313 self.fn = None
311 self.givenargs = []
314 self.givenargs = []
312 self.opts = []
315 self.opts = []
313 self.help = ''
316 self.help = ''
314 self.badalias = None
317 self.badalias = None
315 self.unknowncmd = False
318 self.unknowncmd = False
316 self.source = source
319 self.source = source
317
320
318 try:
321 try:
319 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
322 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
320 for alias, e in cmdtable.iteritems():
323 for alias, e in cmdtable.iteritems():
321 if e is entry:
324 if e is entry:
322 self.cmd = alias
325 self.cmd = alias
323 break
326 break
324 self.shadows = True
327 self.shadows = True
325 except error.UnknownCommand:
328 except error.UnknownCommand:
326 self.shadows = False
329 self.shadows = False
327
330
328 if not self.definition:
331 if not self.definition:
329 self.badalias = _("no definition for alias '%s'") % self.name
332 self.badalias = _("no definition for alias '%s'") % self.name
330 return
333 return
331
334
332 if self.definition.startswith('!'):
335 if self.definition.startswith('!'):
333 self.shell = True
336 self.shell = True
334 def fn(ui, *args):
337 def fn(ui, *args):
335 env = {'HG_ARGS': ' '.join((self.name,) + args)}
338 env = {'HG_ARGS': ' '.join((self.name,) + args)}
336 def _checkvar(m):
339 def _checkvar(m):
337 if m.groups()[0] == '$':
340 if m.groups()[0] == '$':
338 return m.group()
341 return m.group()
339 elif int(m.groups()[0]) <= len(args):
342 elif int(m.groups()[0]) <= len(args):
340 return m.group()
343 return m.group()
341 else:
344 else:
342 ui.debug("No argument found for substitution "
345 ui.debug("No argument found for substitution "
343 "of %i variable in alias '%s' definition."
346 "of %i variable in alias '%s' definition."
344 % (int(m.groups()[0]), self.name))
347 % (int(m.groups()[0]), self.name))
345 return ''
348 return ''
346 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
349 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
347 cmd = aliasinterpolate(self.name, args, cmd)
350 cmd = aliasinterpolate(self.name, args, cmd)
348 return ui.system(cmd, environ=env)
351 return ui.system(cmd, environ=env)
349 self.fn = fn
352 self.fn = fn
350 return
353 return
351
354
352 try:
355 try:
353 args = pycompat.shlexsplit(self.definition)
356 args = pycompat.shlexsplit(self.definition)
354 except ValueError as inst:
357 except ValueError as inst:
355 self.badalias = (_("error in definition for alias '%s': %s")
358 self.badalias = (_("error in definition for alias '%s': %s")
356 % (self.name, inst))
359 % (self.name, inst))
357 return
360 return
358 self.cmdname = cmd = args.pop(0)
361 self.cmdname = cmd = args.pop(0)
359 self.givenargs = args
362 self.givenargs = args
360
363
361 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
364 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
362 if _earlygetopt([invalidarg], args):
365 if _earlygetopt([invalidarg], args):
363 self.badalias = (_("error in definition for alias '%s': %s may "
366 self.badalias = (_("error in definition for alias '%s': %s may "
364 "only be given on the command line")
367 "only be given on the command line")
365 % (self.name, invalidarg))
368 % (self.name, invalidarg))
366 return
369 return
367
370
368 try:
371 try:
369 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
372 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
370 if len(tableentry) > 2:
373 if len(tableentry) > 2:
371 self.fn, self.opts, self.help = tableentry
374 self.fn, self.opts, self.help = tableentry
372 else:
375 else:
373 self.fn, self.opts = tableentry
376 self.fn, self.opts = tableentry
374
377
375 if self.help.startswith("hg " + cmd):
378 if self.help.startswith("hg " + cmd):
376 # drop prefix in old-style help lines so hg shows the alias
379 # drop prefix in old-style help lines so hg shows the alias
377 self.help = self.help[4 + len(cmd):]
380 self.help = self.help[4 + len(cmd):]
378 self.__doc__ = self.fn.__doc__
381 self.__doc__ = self.fn.__doc__
379
382
380 except error.UnknownCommand:
383 except error.UnknownCommand:
381 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
384 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
382 % (self.name, cmd))
385 % (self.name, cmd))
383 self.unknowncmd = True
386 self.unknowncmd = True
384 except error.AmbiguousCommand:
387 except error.AmbiguousCommand:
385 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
388 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
386 % (self.name, cmd))
389 % (self.name, cmd))
387
390
388 @property
391 @property
389 def args(self):
392 def args(self):
390 args = map(util.expandpath, self.givenargs)
393 args = map(util.expandpath, self.givenargs)
391 return aliasargs(self.fn, args)
394 return aliasargs(self.fn, args)
392
395
393 def __getattr__(self, name):
396 def __getattr__(self, name):
394 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
397 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
395 if name not in adefaults:
398 if name not in adefaults:
396 raise AttributeError(name)
399 raise AttributeError(name)
397 if self.badalias or util.safehasattr(self, 'shell'):
400 if self.badalias or util.safehasattr(self, 'shell'):
398 return adefaults[name]
401 return adefaults[name]
399 return getattr(self.fn, name)
402 return getattr(self.fn, name)
400
403
401 def __call__(self, ui, *args, **opts):
404 def __call__(self, ui, *args, **opts):
402 if self.badalias:
405 if self.badalias:
403 hint = None
406 hint = None
404 if self.unknowncmd:
407 if self.unknowncmd:
405 try:
408 try:
406 # check if the command is in a disabled extension
409 # check if the command is in a disabled extension
407 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
410 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
408 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
411 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
409 except error.UnknownCommand:
412 except error.UnknownCommand:
410 pass
413 pass
411 raise error.Abort(self.badalias, hint=hint)
414 raise error.Abort(self.badalias, hint=hint)
412 if self.shadows:
415 if self.shadows:
413 ui.debug("alias '%s' shadows command '%s'\n" %
416 ui.debug("alias '%s' shadows command '%s'\n" %
414 (self.name, self.cmdname))
417 (self.name, self.cmdname))
415
418
416 ui.log('commandalias', "alias '%s' expands to '%s'\n",
419 ui.log('commandalias', "alias '%s' expands to '%s'\n",
417 self.name, self.definition)
420 self.name, self.definition)
418 if util.safehasattr(self, 'shell'):
421 if util.safehasattr(self, 'shell'):
419 return self.fn(ui, *args, **opts)
422 return self.fn(ui, *args, **opts)
420 else:
423 else:
421 try:
424 try:
422 return util.checksignature(self.fn)(ui, *args, **opts)
425 return util.checksignature(self.fn)(ui, *args, **opts)
423 except error.SignatureError:
426 except error.SignatureError:
424 args = ' '.join([self.cmdname] + self.args)
427 args = ' '.join([self.cmdname] + self.args)
425 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
428 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
426 raise
429 raise
427
430
428 def addaliases(ui, cmdtable):
431 def addaliases(ui, cmdtable):
429 # aliases are processed after extensions have been loaded, so they
432 # aliases are processed after extensions have been loaded, so they
430 # may use extension commands. Aliases can also use other alias definitions,
433 # may use extension commands. Aliases can also use other alias definitions,
431 # but only if they have been defined prior to the current definition.
434 # but only if they have been defined prior to the current definition.
432 for alias, definition in ui.configitems('alias'):
435 for alias, definition in ui.configitems('alias'):
433 source = ui.configsource('alias', alias)
436 source = ui.configsource('alias', alias)
434 aliasdef = cmdalias(alias, definition, cmdtable, source)
437 aliasdef = cmdalias(alias, definition, cmdtable, source)
435
438
436 try:
439 try:
437 olddef = cmdtable[aliasdef.cmd][0]
440 olddef = cmdtable[aliasdef.cmd][0]
438 if olddef.definition == aliasdef.definition:
441 if olddef.definition == aliasdef.definition:
439 continue
442 continue
440 except (KeyError, AttributeError):
443 except (KeyError, AttributeError):
441 # definition might not exist or it might not be a cmdalias
444 # definition might not exist or it might not be a cmdalias
442 pass
445 pass
443
446
444 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
447 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
445
448
446 def _parse(ui, args):
449 def _parse(ui, args):
447 options = {}
450 options = {}
448 cmdoptions = {}
451 cmdoptions = {}
449
452
450 try:
453 try:
451 args = fancyopts.fancyopts(args, commands.globalopts, options)
454 args = fancyopts.fancyopts(args, commands.globalopts, options)
452 except getopt.GetoptError as inst:
455 except getopt.GetoptError as inst:
453 raise error.CommandError(None, inst)
456 raise error.CommandError(None, inst)
454
457
455 if args:
458 if args:
456 cmd, args = args[0], args[1:]
459 cmd, args = args[0], args[1:]
457 aliases, entry = cmdutil.findcmd(cmd, commands.table,
460 aliases, entry = cmdutil.findcmd(cmd, commands.table,
458 ui.configbool("ui", "strict"))
461 ui.configbool("ui", "strict"))
459 cmd = aliases[0]
462 cmd = aliases[0]
460 args = aliasargs(entry[0], args)
463 args = aliasargs(entry[0], args)
461 defaults = ui.config("defaults", cmd)
464 defaults = ui.config("defaults", cmd)
462 if defaults:
465 if defaults:
463 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
466 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
464 c = list(entry[1])
467 c = list(entry[1])
465 else:
468 else:
466 cmd = None
469 cmd = None
467 c = []
470 c = []
468
471
469 # combine global options into local
472 # combine global options into local
470 for o in commands.globalopts:
473 for o in commands.globalopts:
471 c.append((o[0], o[1], options[o[1]], o[3]))
474 c.append((o[0], o[1], options[o[1]], o[3]))
472
475
473 try:
476 try:
474 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
477 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
475 except getopt.GetoptError as inst:
478 except getopt.GetoptError as inst:
476 raise error.CommandError(cmd, inst)
479 raise error.CommandError(cmd, inst)
477
480
478 # separate global options back out
481 # separate global options back out
479 for o in commands.globalopts:
482 for o in commands.globalopts:
480 n = o[1]
483 n = o[1]
481 options[n] = cmdoptions[n]
484 options[n] = cmdoptions[n]
482 del cmdoptions[n]
485 del cmdoptions[n]
483
486
484 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
487 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
485
488
486 def _parseconfig(ui, config):
489 def _parseconfig(ui, config):
487 """parse the --config options from the command line"""
490 """parse the --config options from the command line"""
488 configs = []
491 configs = []
489
492
490 for cfg in config:
493 for cfg in config:
491 try:
494 try:
492 name, value = [cfgelem.strip()
495 name, value = [cfgelem.strip()
493 for cfgelem in cfg.split('=', 1)]
496 for cfgelem in cfg.split('=', 1)]
494 section, name = name.split('.', 1)
497 section, name = name.split('.', 1)
495 if not section or not name:
498 if not section or not name:
496 raise IndexError
499 raise IndexError
497 ui.setconfig(section, name, value, '--config')
500 ui.setconfig(section, name, value, '--config')
498 configs.append((section, name, value))
501 configs.append((section, name, value))
499 except (IndexError, ValueError):
502 except (IndexError, ValueError):
500 raise error.Abort(_('malformed --config option: %r '
503 raise error.Abort(_('malformed --config option: %r '
501 '(use --config section.name=value)') % cfg)
504 '(use --config section.name=value)') % cfg)
502
505
503 return configs
506 return configs
504
507
505 def _earlygetopt(aliases, args):
508 def _earlygetopt(aliases, args):
506 """Return list of values for an option (or aliases).
509 """Return list of values for an option (or aliases).
507
510
508 The values are listed in the order they appear in args.
511 The values are listed in the order they appear in args.
509 The options and values are removed from args.
512 The options and values are removed from args.
510
513
511 >>> args = ['x', '--cwd', 'foo', 'y']
514 >>> args = ['x', '--cwd', 'foo', 'y']
512 >>> _earlygetopt(['--cwd'], args), args
515 >>> _earlygetopt(['--cwd'], args), args
513 (['foo'], ['x', 'y'])
516 (['foo'], ['x', 'y'])
514
517
515 >>> args = ['x', '--cwd=bar', 'y']
518 >>> args = ['x', '--cwd=bar', 'y']
516 >>> _earlygetopt(['--cwd'], args), args
519 >>> _earlygetopt(['--cwd'], args), args
517 (['bar'], ['x', 'y'])
520 (['bar'], ['x', 'y'])
518
521
519 >>> args = ['x', '-R', 'foo', 'y']
522 >>> args = ['x', '-R', 'foo', 'y']
520 >>> _earlygetopt(['-R'], args), args
523 >>> _earlygetopt(['-R'], args), args
521 (['foo'], ['x', 'y'])
524 (['foo'], ['x', 'y'])
522
525
523 >>> args = ['x', '-Rbar', 'y']
526 >>> args = ['x', '-Rbar', 'y']
524 >>> _earlygetopt(['-R'], args), args
527 >>> _earlygetopt(['-R'], args), args
525 (['bar'], ['x', 'y'])
528 (['bar'], ['x', 'y'])
526 """
529 """
527 try:
530 try:
528 argcount = args.index("--")
531 argcount = args.index("--")
529 except ValueError:
532 except ValueError:
530 argcount = len(args)
533 argcount = len(args)
531 shortopts = [opt for opt in aliases if len(opt) == 2]
534 shortopts = [opt for opt in aliases if len(opt) == 2]
532 values = []
535 values = []
533 pos = 0
536 pos = 0
534 while pos < argcount:
537 while pos < argcount:
535 fullarg = arg = args[pos]
538 fullarg = arg = args[pos]
536 equals = arg.find('=')
539 equals = arg.find('=')
537 if equals > -1:
540 if equals > -1:
538 arg = arg[:equals]
541 arg = arg[:equals]
539 if arg in aliases:
542 if arg in aliases:
540 del args[pos]
543 del args[pos]
541 if equals > -1:
544 if equals > -1:
542 values.append(fullarg[equals + 1:])
545 values.append(fullarg[equals + 1:])
543 argcount -= 1
546 argcount -= 1
544 else:
547 else:
545 if pos + 1 >= argcount:
548 if pos + 1 >= argcount:
546 # ignore and let getopt report an error if there is no value
549 # ignore and let getopt report an error if there is no value
547 break
550 break
548 values.append(args.pop(pos))
551 values.append(args.pop(pos))
549 argcount -= 2
552 argcount -= 2
550 elif arg[:2] in shortopts:
553 elif arg[:2] in shortopts:
551 # short option can have no following space, e.g. hg log -Rfoo
554 # short option can have no following space, e.g. hg log -Rfoo
552 values.append(args.pop(pos)[2:])
555 values.append(args.pop(pos)[2:])
553 argcount -= 1
556 argcount -= 1
554 else:
557 else:
555 pos += 1
558 pos += 1
556 return values
559 return values
557
560
558 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
561 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
559 # run pre-hook, and abort if it fails
562 # run pre-hook, and abort if it fails
560 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
563 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
561 pats=cmdpats, opts=cmdoptions)
564 pats=cmdpats, opts=cmdoptions)
562 try:
565 try:
563 ret = _runcommand(ui, options, cmd, d)
566 ret = _runcommand(ui, options, cmd, d)
564 # run post-hook, passing command result
567 # run post-hook, passing command result
565 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
568 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
566 result=ret, pats=cmdpats, opts=cmdoptions)
569 result=ret, pats=cmdpats, opts=cmdoptions)
567 except Exception:
570 except Exception:
568 # run failure hook and re-raise
571 # run failure hook and re-raise
569 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
572 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
570 pats=cmdpats, opts=cmdoptions)
573 pats=cmdpats, opts=cmdoptions)
571 raise
574 raise
572 return ret
575 return ret
573
576
574 def _getlocal(ui, rpath, wd=None):
577 def _getlocal(ui, rpath, wd=None):
575 """Return (path, local ui object) for the given target path.
578 """Return (path, local ui object) for the given target path.
576
579
577 Takes paths in [cwd]/.hg/hgrc into account."
580 Takes paths in [cwd]/.hg/hgrc into account."
578 """
581 """
579 if wd is None:
582 if wd is None:
580 try:
583 try:
581 wd = pycompat.getcwd()
584 wd = pycompat.getcwd()
582 except OSError as e:
585 except OSError as e:
583 raise error.Abort(_("error getting current working directory: %s") %
586 raise error.Abort(_("error getting current working directory: %s") %
584 e.strerror)
587 e.strerror)
585 path = cmdutil.findrepo(wd) or ""
588 path = cmdutil.findrepo(wd) or ""
586 if not path:
589 if not path:
587 lui = ui
590 lui = ui
588 else:
591 else:
589 lui = ui.copy()
592 lui = ui.copy()
590 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
593 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
591
594
592 if rpath and rpath[-1]:
595 if rpath and rpath[-1]:
593 path = lui.expandpath(rpath[-1])
596 path = lui.expandpath(rpath[-1])
594 lui = ui.copy()
597 lui = ui.copy()
595 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
598 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
596
599
597 return path, lui
600 return path, lui
598
601
599 def _checkshellalias(lui, ui, args):
602 def _checkshellalias(lui, ui, args):
600 """Return the function to run the shell alias, if it is required"""
603 """Return the function to run the shell alias, if it is required"""
601 options = {}
604 options = {}
602
605
603 try:
606 try:
604 args = fancyopts.fancyopts(args, commands.globalopts, options)
607 args = fancyopts.fancyopts(args, commands.globalopts, options)
605 except getopt.GetoptError:
608 except getopt.GetoptError:
606 return
609 return
607
610
608 if not args:
611 if not args:
609 return
612 return
610
613
611 cmdtable = commands.table
614 cmdtable = commands.table
612
615
613 cmd = args[0]
616 cmd = args[0]
614 try:
617 try:
615 strict = ui.configbool("ui", "strict")
618 strict = ui.configbool("ui", "strict")
616 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
619 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
617 except (error.AmbiguousCommand, error.UnknownCommand):
620 except (error.AmbiguousCommand, error.UnknownCommand):
618 return
621 return
619
622
620 cmd = aliases[0]
623 cmd = aliases[0]
621 fn = entry[0]
624 fn = entry[0]
622
625
623 if cmd and util.safehasattr(fn, 'shell'):
626 if cmd and util.safehasattr(fn, 'shell'):
624 d = lambda: fn(ui, *args[1:])
627 d = lambda: fn(ui, *args[1:])
625 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
628 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
626 [], {})
629 [], {})
627
630
628 _loaded = set()
631 _loaded = set()
629
632
630 # list of (objname, loadermod, loadername) tuple:
633 # list of (objname, loadermod, loadername) tuple:
631 # - objname is the name of an object in extension module, from which
634 # - objname is the name of an object in extension module, from which
632 # extra information is loaded
635 # extra information is loaded
633 # - loadermod is the module where loader is placed
636 # - loadermod is the module where loader is placed
634 # - loadername is the name of the function, which takes (ui, extensionname,
637 # - loadername is the name of the function, which takes (ui, extensionname,
635 # extraobj) arguments
638 # extraobj) arguments
636 extraloaders = [
639 extraloaders = [
637 ('cmdtable', commands, 'loadcmdtable'),
640 ('cmdtable', commands, 'loadcmdtable'),
638 ('colortable', color, 'loadcolortable'),
641 ('colortable', color, 'loadcolortable'),
639 ('filesetpredicate', fileset, 'loadpredicate'),
642 ('filesetpredicate', fileset, 'loadpredicate'),
640 ('revsetpredicate', revset, 'loadpredicate'),
643 ('revsetpredicate', revset, 'loadpredicate'),
641 ('templatefilter', templatefilters, 'loadfilter'),
644 ('templatefilter', templatefilters, 'loadfilter'),
642 ('templatefunc', templater, 'loadfunction'),
645 ('templatefunc', templater, 'loadfunction'),
643 ('templatekeyword', templatekw, 'loadkeyword'),
646 ('templatekeyword', templatekw, 'loadkeyword'),
644 ]
647 ]
645
648
646 def _dispatch(req):
649 def _dispatch(req):
647 args = req.args
650 args = req.args
648 ui = req.ui
651 ui = req.ui
649
652
650 # check for cwd
653 # check for cwd
651 cwd = _earlygetopt(['--cwd'], args)
654 cwd = _earlygetopt(['--cwd'], args)
652 if cwd:
655 if cwd:
653 os.chdir(cwd[-1])
656 os.chdir(cwd[-1])
654
657
655 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
658 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
656 path, lui = _getlocal(ui, rpath)
659 path, lui = _getlocal(ui, rpath)
657
660
658 # Side-effect of accessing is debugcommands module is guaranteed to be
661 # Side-effect of accessing is debugcommands module is guaranteed to be
659 # imported and commands.table is populated.
662 # imported and commands.table is populated.
660 debugcommands.command
663 debugcommands.command
661
664
662 uis = set([ui, lui])
665 uis = set([ui, lui])
663
666
664 if req.repo:
667 if req.repo:
665 uis.add(req.repo.ui)
668 uis.add(req.repo.ui)
666
669
667 if '--profile' in args:
670 if '--profile' in args:
668 for ui_ in uis:
671 for ui_ in uis:
669 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
672 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
670
673
671 with profiling.maybeprofile(lui):
674 with profiling.maybeprofile(lui):
672 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
675 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
673 # reposetup. Programs like TortoiseHg will call _dispatch several
676 # reposetup. Programs like TortoiseHg will call _dispatch several
674 # times so we keep track of configured extensions in _loaded.
677 # times so we keep track of configured extensions in _loaded.
675 extensions.loadall(lui)
678 extensions.loadall(lui)
676 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
679 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
677 # Propagate any changes to lui.__class__ by extensions
680 # Propagate any changes to lui.__class__ by extensions
678 ui.__class__ = lui.__class__
681 ui.__class__ = lui.__class__
679
682
680 # (uisetup and extsetup are handled in extensions.loadall)
683 # (uisetup and extsetup are handled in extensions.loadall)
681
684
682 for name, module in exts:
685 for name, module in exts:
683 for objname, loadermod, loadername in extraloaders:
686 for objname, loadermod, loadername in extraloaders:
684 extraobj = getattr(module, objname, None)
687 extraobj = getattr(module, objname, None)
685 if extraobj is not None:
688 if extraobj is not None:
686 getattr(loadermod, loadername)(ui, name, extraobj)
689 getattr(loadermod, loadername)(ui, name, extraobj)
687 _loaded.add(name)
690 _loaded.add(name)
688
691
689 # (reposetup is handled in hg.repository)
692 # (reposetup is handled in hg.repository)
690
693
691 addaliases(lui, commands.table)
694 addaliases(lui, commands.table)
692
695
693 # All aliases and commands are completely defined, now.
696 # All aliases and commands are completely defined, now.
694 # Check abbreviation/ambiguity of shell alias.
697 # Check abbreviation/ambiguity of shell alias.
695 shellaliasfn = _checkshellalias(lui, ui, args)
698 shellaliasfn = _checkshellalias(lui, ui, args)
696 if shellaliasfn:
699 if shellaliasfn:
697 return shellaliasfn()
700 return shellaliasfn()
698
701
699 # check for fallback encoding
702 # check for fallback encoding
700 fallback = lui.config('ui', 'fallbackencoding')
703 fallback = lui.config('ui', 'fallbackencoding')
701 if fallback:
704 if fallback:
702 encoding.fallbackencoding = fallback
705 encoding.fallbackencoding = fallback
703
706
704 fullargs = args
707 fullargs = args
705 cmd, func, args, options, cmdoptions = _parse(lui, args)
708 cmd, func, args, options, cmdoptions = _parse(lui, args)
706
709
707 if options["config"]:
710 if options["config"]:
708 raise error.Abort(_("option --config may not be abbreviated!"))
711 raise error.Abort(_("option --config may not be abbreviated!"))
709 if options["cwd"]:
712 if options["cwd"]:
710 raise error.Abort(_("option --cwd may not be abbreviated!"))
713 raise error.Abort(_("option --cwd may not be abbreviated!"))
711 if options["repository"]:
714 if options["repository"]:
712 raise error.Abort(_(
715 raise error.Abort(_(
713 "option -R has to be separated from other options (e.g. not "
716 "option -R has to be separated from other options (e.g. not "
714 "-qR) and --repository may only be abbreviated as --repo!"))
717 "-qR) and --repository may only be abbreviated as --repo!"))
715
718
716 if options["encoding"]:
719 if options["encoding"]:
717 encoding.encoding = options["encoding"]
720 encoding.encoding = options["encoding"]
718 if options["encodingmode"]:
721 if options["encodingmode"]:
719 encoding.encodingmode = options["encodingmode"]
722 encoding.encodingmode = options["encodingmode"]
720 if options["time"]:
723 if options["time"]:
721 def get_times():
724 def get_times():
722 t = os.times()
725 t = os.times()
723 if t[4] == 0.0:
726 if t[4] == 0.0:
724 # Windows leaves this as zero, so use time.clock()
727 # Windows leaves this as zero, so use time.clock()
725 t = (t[0], t[1], t[2], t[3], time.clock())
728 t = (t[0], t[1], t[2], t[3], time.clock())
726 return t
729 return t
727 s = get_times()
730 s = get_times()
728 def print_time():
731 def print_time():
729 t = get_times()
732 t = get_times()
730 ui.warn(
733 ui.warn(
731 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
734 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
732 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
735 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
733 atexit.register(print_time)
736 atexit.register(print_time)
734
737
735 if options['verbose'] or options['debug'] or options['quiet']:
738 if options['verbose'] or options['debug'] or options['quiet']:
736 for opt in ('verbose', 'debug', 'quiet'):
739 for opt in ('verbose', 'debug', 'quiet'):
737 val = str(bool(options[opt]))
740 val = str(bool(options[opt]))
738 for ui_ in uis:
741 for ui_ in uis:
739 ui_.setconfig('ui', opt, val, '--' + opt)
742 ui_.setconfig('ui', opt, val, '--' + opt)
740
743
741 if options['traceback']:
744 if options['traceback']:
742 for ui_ in uis:
745 for ui_ in uis:
743 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
746 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
744
747
745 if options['noninteractive']:
748 if options['noninteractive']:
746 for ui_ in uis:
749 for ui_ in uis:
747 ui_.setconfig('ui', 'interactive', 'off', '-y')
750 ui_.setconfig('ui', 'interactive', 'off', '-y')
748
751
749 if cmdoptions.get('insecure', False):
752 if cmdoptions.get('insecure', False):
750 for ui_ in uis:
753 for ui_ in uis:
751 ui_.insecureconnections = True
754 ui_.insecureconnections = True
752
755
753 if options['version']:
756 if options['version']:
754 return commands.version_(ui)
757 return commands.version_(ui)
755 if options['help']:
758 if options['help']:
756 return commands.help_(ui, cmd, command=cmd is not None)
759 return commands.help_(ui, cmd, command=cmd is not None)
757 elif not cmd:
760 elif not cmd:
758 return commands.help_(ui, 'shortlist')
761 return commands.help_(ui, 'shortlist')
759
762
760 repo = None
763 repo = None
761 cmdpats = args[:]
764 cmdpats = args[:]
762 if not func.norepo:
765 if not func.norepo:
763 # use the repo from the request only if we don't have -R
766 # use the repo from the request only if we don't have -R
764 if not rpath and not cwd:
767 if not rpath and not cwd:
765 repo = req.repo
768 repo = req.repo
766
769
767 if repo:
770 if repo:
768 # set the descriptors of the repo ui to those of ui
771 # set the descriptors of the repo ui to those of ui
769 repo.ui.fin = ui.fin
772 repo.ui.fin = ui.fin
770 repo.ui.fout = ui.fout
773 repo.ui.fout = ui.fout
771 repo.ui.ferr = ui.ferr
774 repo.ui.ferr = ui.ferr
772 else:
775 else:
773 try:
776 try:
774 repo = hg.repository(ui, path=path)
777 repo = hg.repository(ui, path=path)
775 if not repo.local():
778 if not repo.local():
776 raise error.Abort(_("repository '%s' is not local")
779 raise error.Abort(_("repository '%s' is not local")
777 % path)
780 % path)
778 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
781 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
779 'repo')
782 'repo')
780 except error.RequirementError:
783 except error.RequirementError:
781 raise
784 raise
782 except error.RepoError:
785 except error.RepoError:
783 if rpath and rpath[-1]: # invalid -R path
786 if rpath and rpath[-1]: # invalid -R path
784 raise
787 raise
785 if not func.optionalrepo:
788 if not func.optionalrepo:
786 if func.inferrepo and args and not path:
789 if func.inferrepo and args and not path:
787 # try to infer -R from command args
790 # try to infer -R from command args
788 repos = map(cmdutil.findrepo, args)
791 repos = map(cmdutil.findrepo, args)
789 guess = repos[0]
792 guess = repos[0]
790 if guess and repos.count(guess) == len(repos):
793 if guess and repos.count(guess) == len(repos):
791 req.args = ['--repository', guess] + fullargs
794 req.args = ['--repository', guess] + fullargs
792 return _dispatch(req)
795 return _dispatch(req)
793 if not path:
796 if not path:
794 raise error.RepoError(_("no repository found in"
797 raise error.RepoError(_("no repository found in"
795 " '%s' (.hg not found)")
798 " '%s' (.hg not found)")
796 % pycompat.getcwd())
799 % pycompat.getcwd())
797 raise
800 raise
798 if repo:
801 if repo:
799 ui = repo.ui
802 ui = repo.ui
800 if options['hidden']:
803 if options['hidden']:
801 repo = repo.unfiltered()
804 repo = repo.unfiltered()
802 args.insert(0, repo)
805 args.insert(0, repo)
803 elif rpath:
806 elif rpath:
804 ui.warn(_("warning: --repository ignored\n"))
807 ui.warn(_("warning: --repository ignored\n"))
805
808
806 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
809 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
807 ui.log("command", '%s\n', msg)
810 ui.log("command", '%s\n', msg)
808 strcmdopt = pycompat.strkwargs(cmdoptions)
811 strcmdopt = pycompat.strkwargs(cmdoptions)
809 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
812 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
810 try:
813 try:
811 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
814 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
812 cmdpats, cmdoptions)
815 cmdpats, cmdoptions)
813 finally:
816 finally:
814 if repo and repo != req.repo:
817 if repo and repo != req.repo:
815 repo.close()
818 repo.close()
816
819
817 def _runcommand(ui, options, cmd, cmdfunc):
820 def _runcommand(ui, options, cmd, cmdfunc):
818 """Run a command function, possibly with profiling enabled."""
821 """Run a command function, possibly with profiling enabled."""
819 try:
822 try:
820 return cmdfunc()
823 return cmdfunc()
821 except error.SignatureError:
824 except error.SignatureError:
822 raise error.CommandError(cmd, _('invalid arguments'))
825 raise error.CommandError(cmd, _('invalid arguments'))
823
826
824 def _exceptionwarning(ui):
827 def _exceptionwarning(ui):
825 """Produce a warning message for the current active exception"""
828 """Produce a warning message for the current active exception"""
826
829
827 # For compatibility checking, we discard the portion of the hg
830 # For compatibility checking, we discard the portion of the hg
828 # version after the + on the assumption that if a "normal
831 # version after the + on the assumption that if a "normal
829 # user" is running a build with a + in it the packager
832 # user" is running a build with a + in it the packager
830 # probably built from fairly close to a tag and anyone with a
833 # probably built from fairly close to a tag and anyone with a
831 # 'make local' copy of hg (where the version number can be out
834 # 'make local' copy of hg (where the version number can be out
832 # of date) will be clueful enough to notice the implausible
835 # of date) will be clueful enough to notice the implausible
833 # version number and try updating.
836 # version number and try updating.
834 ct = util.versiontuple(n=2)
837 ct = util.versiontuple(n=2)
835 worst = None, ct, ''
838 worst = None, ct, ''
836 if ui.config('ui', 'supportcontact', None) is None:
839 if ui.config('ui', 'supportcontact', None) is None:
837 for name, mod in extensions.extensions():
840 for name, mod in extensions.extensions():
838 testedwith = getattr(mod, 'testedwith', '')
841 testedwith = getattr(mod, 'testedwith', '')
839 report = getattr(mod, 'buglink', _('the extension author.'))
842 report = getattr(mod, 'buglink', _('the extension author.'))
840 if not testedwith.strip():
843 if not testedwith.strip():
841 # We found an untested extension. It's likely the culprit.
844 # We found an untested extension. It's likely the culprit.
842 worst = name, 'unknown', report
845 worst = name, 'unknown', report
843 break
846 break
844
847
845 # Never blame on extensions bundled with Mercurial.
848 # Never blame on extensions bundled with Mercurial.
846 if extensions.ismoduleinternal(mod):
849 if extensions.ismoduleinternal(mod):
847 continue
850 continue
848
851
849 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
852 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
850 if ct in tested:
853 if ct in tested:
851 continue
854 continue
852
855
853 lower = [t for t in tested if t < ct]
856 lower = [t for t in tested if t < ct]
854 nearest = max(lower or tested)
857 nearest = max(lower or tested)
855 if worst[0] is None or nearest < worst[1]:
858 if worst[0] is None or nearest < worst[1]:
856 worst = name, nearest, report
859 worst = name, nearest, report
857 if worst[0] is not None:
860 if worst[0] is not None:
858 name, testedwith, report = worst
861 name, testedwith, report = worst
859 if not isinstance(testedwith, str):
862 if not isinstance(testedwith, str):
860 testedwith = '.'.join([str(c) for c in testedwith])
863 testedwith = '.'.join([str(c) for c in testedwith])
861 warning = (_('** Unknown exception encountered with '
864 warning = (_('** Unknown exception encountered with '
862 'possibly-broken third-party extension %s\n'
865 'possibly-broken third-party extension %s\n'
863 '** which supports versions %s of Mercurial.\n'
866 '** which supports versions %s of Mercurial.\n'
864 '** Please disable %s and try your action again.\n'
867 '** Please disable %s and try your action again.\n'
865 '** If that fixes the bug please report it to %s\n')
868 '** If that fixes the bug please report it to %s\n')
866 % (name, testedwith, name, report))
869 % (name, testedwith, name, report))
867 else:
870 else:
868 bugtracker = ui.config('ui', 'supportcontact', None)
871 bugtracker = ui.config('ui', 'supportcontact', None)
869 if bugtracker is None:
872 if bugtracker is None:
870 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
873 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
871 warning = (_("** unknown exception encountered, "
874 warning = (_("** unknown exception encountered, "
872 "please report by visiting\n** ") + bugtracker + '\n')
875 "please report by visiting\n** ") + bugtracker + '\n')
873 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
876 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
874 (_("** Mercurial Distributed SCM (version %s)\n") %
877 (_("** Mercurial Distributed SCM (version %s)\n") %
875 util.version()) +
878 util.version()) +
876 (_("** Extensions loaded: %s\n") %
879 (_("** Extensions loaded: %s\n") %
877 ", ".join([x[0] for x in extensions.extensions()])))
880 ", ".join([x[0] for x in extensions.extensions()])))
878 return warning
881 return warning
879
882
880 def handlecommandexception(ui):
883 def handlecommandexception(ui):
881 """Produce a warning message for broken commands
884 """Produce a warning message for broken commands
882
885
883 Called when handling an exception; the exception is reraised if
886 Called when handling an exception; the exception is reraised if
884 this function returns False, ignored otherwise.
887 this function returns False, ignored otherwise.
885 """
888 """
886 warning = _exceptionwarning(ui)
889 warning = _exceptionwarning(ui)
887 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
890 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
888 ui.warn(warning)
891 ui.warn(warning)
889 return False # re-raise the exception
892 return False # re-raise the exception
@@ -1,1478 +1,1493
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 contextlib
11 import contextlib
11 import errno
12 import errno
12 import getpass
13 import getpass
13 import inspect
14 import inspect
14 import os
15 import os
15 import re
16 import re
16 import socket
17 import socket
17 import sys
18 import sys
18 import tempfile
19 import tempfile
19 import traceback
20 import traceback
20
21
21 from .i18n import _
22 from .i18n import _
22 from .node import hex
23 from .node import hex
23
24
24 from . import (
25 from . import (
25 config,
26 config,
26 encoding,
27 encoding,
27 error,
28 error,
28 formatter,
29 formatter,
29 progress,
30 progress,
30 pycompat,
31 pycompat,
31 scmutil,
32 scmutil,
32 util,
33 util,
33 )
34 )
34
35
35 urlreq = util.urlreq
36 urlreq = util.urlreq
36
37
37 samplehgrcs = {
38 samplehgrcs = {
38 'user':
39 'user':
39 """# example user config (see 'hg help config' for more info)
40 """# example user config (see 'hg help config' for more info)
40 [ui]
41 [ui]
41 # name and email, e.g.
42 # name and email, e.g.
42 # username = Jane Doe <jdoe@example.com>
43 # username = Jane Doe <jdoe@example.com>
43 username =
44 username =
44
45
45 [extensions]
46 [extensions]
46 # uncomment these lines to enable some popular extensions
47 # uncomment these lines to enable some popular extensions
47 # (see 'hg help extensions' for more info)
48 # (see 'hg help extensions' for more info)
48 #
49 #
49 # pager =
50 # pager =
50 # color =""",
51 # color =""",
51
52
52 'cloned':
53 'cloned':
53 """# example repository config (see 'hg help config' for more info)
54 """# example repository config (see 'hg help config' for more info)
54 [paths]
55 [paths]
55 default = %s
56 default = %s
56
57
57 # path aliases to other clones of this repo in URLs or filesystem paths
58 # path aliases to other clones of this repo in URLs or filesystem paths
58 # (see 'hg help config.paths' for more info)
59 # (see 'hg help config.paths' for more info)
59 #
60 #
60 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
61 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
61 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
62 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
62 # my-clone = /home/jdoe/jdoes-clone
63 # my-clone = /home/jdoe/jdoes-clone
63
64
64 [ui]
65 [ui]
65 # name and email (local to this repository, optional), e.g.
66 # name and email (local to this repository, optional), e.g.
66 # username = Jane Doe <jdoe@example.com>
67 # username = Jane Doe <jdoe@example.com>
67 """,
68 """,
68
69
69 'local':
70 'local':
70 """# example repository config (see 'hg help config' for more info)
71 """# example repository config (see 'hg help config' for more info)
71 [paths]
72 [paths]
72 # path aliases to other clones of this repo in URLs or filesystem paths
73 # path aliases to other clones of this repo in URLs or filesystem paths
73 # (see 'hg help config.paths' for more info)
74 # (see 'hg help config.paths' for more info)
74 #
75 #
75 # default = http://example.com/hg/example-repo
76 # default = http://example.com/hg/example-repo
76 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
77 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
77 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
78 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
78 # my-clone = /home/jdoe/jdoes-clone
79 # my-clone = /home/jdoe/jdoes-clone
79
80
80 [ui]
81 [ui]
81 # name and email (local to this repository, optional), e.g.
82 # name and email (local to this repository, optional), e.g.
82 # username = Jane Doe <jdoe@example.com>
83 # username = Jane Doe <jdoe@example.com>
83 """,
84 """,
84
85
85 'global':
86 'global':
86 """# example system-wide hg config (see 'hg help config' for more info)
87 """# example system-wide hg config (see 'hg help config' for more info)
87
88
88 [extensions]
89 [extensions]
89 # uncomment these lines to enable some popular extensions
90 # uncomment these lines to enable some popular extensions
90 # (see 'hg help extensions' for more info)
91 # (see 'hg help extensions' for more info)
91 #
92 #
92 # blackbox =
93 # blackbox =
93 # color =
94 # color =
94 # pager =""",
95 # pager =""",
95 }
96 }
96
97
97
98
98 class httppasswordmgrdbproxy(object):
99 class httppasswordmgrdbproxy(object):
99 """Delays loading urllib2 until it's needed."""
100 """Delays loading urllib2 until it's needed."""
100 def __init__(self):
101 def __init__(self):
101 self._mgr = None
102 self._mgr = None
102
103
103 def _get_mgr(self):
104 def _get_mgr(self):
104 if self._mgr is None:
105 if self._mgr is None:
105 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
106 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
106 return self._mgr
107 return self._mgr
107
108
108 def add_password(self, *args, **kwargs):
109 def add_password(self, *args, **kwargs):
109 return self._get_mgr().add_password(*args, **kwargs)
110 return self._get_mgr().add_password(*args, **kwargs)
110
111
111 def find_user_password(self, *args, **kwargs):
112 def find_user_password(self, *args, **kwargs):
112 return self._get_mgr().find_user_password(*args, **kwargs)
113 return self._get_mgr().find_user_password(*args, **kwargs)
113
114
114
115
115 class ui(object):
116 class ui(object):
116 def __init__(self, src=None):
117 def __init__(self, src=None):
117 """Create a fresh new ui object if no src given
118 """Create a fresh new ui object if no src given
118
119
119 Use uimod.ui.load() to create a ui which knows global and user configs.
120 Use uimod.ui.load() to create a ui which knows global and user configs.
120 In most cases, you should use ui.copy() to create a copy of an existing
121 In most cases, you should use ui.copy() to create a copy of an existing
121 ui object.
122 ui object.
122 """
123 """
123 # _buffers: used for temporary capture of output
124 # _buffers: used for temporary capture of output
124 self._buffers = []
125 self._buffers = []
125 # 3-tuple describing how each buffer in the stack behaves.
126 # 3-tuple describing how each buffer in the stack behaves.
126 # Values are (capture stderr, capture subprocesses, apply labels).
127 # Values are (capture stderr, capture subprocesses, apply labels).
127 self._bufferstates = []
128 self._bufferstates = []
128 # When a buffer is active, defines whether we are expanding labels.
129 # When a buffer is active, defines whether we are expanding labels.
129 # This exists to prevent an extra list lookup.
130 # This exists to prevent an extra list lookup.
130 self._bufferapplylabels = None
131 self._bufferapplylabels = None
131 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
132 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
132 self._reportuntrusted = True
133 self._reportuntrusted = True
133 self._ocfg = config.config() # overlay
134 self._ocfg = config.config() # overlay
134 self._tcfg = config.config() # trusted
135 self._tcfg = config.config() # trusted
135 self._ucfg = config.config() # untrusted
136 self._ucfg = config.config() # untrusted
136 self._trustusers = set()
137 self._trustusers = set()
137 self._trustgroups = set()
138 self._trustgroups = set()
138 self.callhooks = True
139 self.callhooks = True
139 # Insecure server connections requested.
140 # Insecure server connections requested.
140 self.insecureconnections = False
141 self.insecureconnections = False
142 # Blocked time
143 self.logblockedtimes = False
141
144
142 if src:
145 if src:
143 self.fout = src.fout
146 self.fout = src.fout
144 self.ferr = src.ferr
147 self.ferr = src.ferr
145 self.fin = src.fin
148 self.fin = src.fin
146
149
147 self._tcfg = src._tcfg.copy()
150 self._tcfg = src._tcfg.copy()
148 self._ucfg = src._ucfg.copy()
151 self._ucfg = src._ucfg.copy()
149 self._ocfg = src._ocfg.copy()
152 self._ocfg = src._ocfg.copy()
150 self._trustusers = src._trustusers.copy()
153 self._trustusers = src._trustusers.copy()
151 self._trustgroups = src._trustgroups.copy()
154 self._trustgroups = src._trustgroups.copy()
152 self.environ = src.environ
155 self.environ = src.environ
153 self.callhooks = src.callhooks
156 self.callhooks = src.callhooks
154 self.insecureconnections = src.insecureconnections
157 self.insecureconnections = src.insecureconnections
155 self.fixconfig()
158 self.fixconfig()
156
159
157 self.httppasswordmgrdb = src.httppasswordmgrdb
160 self.httppasswordmgrdb = src.httppasswordmgrdb
161 self._blockedtimes = src._blockedtimes
158 else:
162 else:
159 self.fout = util.stdout
163 self.fout = util.stdout
160 self.ferr = util.stderr
164 self.ferr = util.stderr
161 self.fin = util.stdin
165 self.fin = util.stdin
162
166
163 # shared read-only environment
167 # shared read-only environment
164 self.environ = encoding.environ
168 self.environ = encoding.environ
165
169
166 self.httppasswordmgrdb = httppasswordmgrdbproxy()
170 self.httppasswordmgrdb = httppasswordmgrdbproxy()
171 self._blockedtimes = collections.defaultdict(int)
167
172
168 allowed = self.configlist('experimental', 'exportableenviron')
173 allowed = self.configlist('experimental', 'exportableenviron')
169 if '*' in allowed:
174 if '*' in allowed:
170 self._exportableenviron = self.environ
175 self._exportableenviron = self.environ
171 else:
176 else:
172 self._exportableenviron = {}
177 self._exportableenviron = {}
173 for k in allowed:
178 for k in allowed:
174 if k in self.environ:
179 if k in self.environ:
175 self._exportableenviron[k] = self.environ[k]
180 self._exportableenviron[k] = self.environ[k]
176
181
177 @classmethod
182 @classmethod
178 def load(cls):
183 def load(cls):
179 """Create a ui and load global and user configs"""
184 """Create a ui and load global and user configs"""
180 u = cls()
185 u = cls()
181 # we always trust global config files
186 # we always trust global config files
182 for f in scmutil.rcpath():
187 for f in scmutil.rcpath():
183 u.readconfig(f, trust=True)
188 u.readconfig(f, trust=True)
184 return u
189 return u
185
190
186 def copy(self):
191 def copy(self):
187 return self.__class__(self)
192 return self.__class__(self)
188
193
189 def resetstate(self):
194 def resetstate(self):
190 """Clear internal state that shouldn't persist across commands"""
195 """Clear internal state that shouldn't persist across commands"""
191 if self._progbar:
196 if self._progbar:
192 self._progbar.resetstate() # reset last-print time of progress bar
197 self._progbar.resetstate() # reset last-print time of progress bar
193 self.httppasswordmgrdb = httppasswordmgrdbproxy()
198 self.httppasswordmgrdb = httppasswordmgrdbproxy()
194
199
200 @contextlib.contextmanager
201 def timeblockedsection(self, key):
202 starttime = util.timer()
203 try:
204 yield
205 finally:
206 self._blockedtimes[key + '_blocked'] += \
207 (util.timer() - starttime) * 1000
208
195 def formatter(self, topic, opts):
209 def formatter(self, topic, opts):
196 return formatter.formatter(self, topic, opts)
210 return formatter.formatter(self, topic, opts)
197
211
198 def _trusted(self, fp, f):
212 def _trusted(self, fp, f):
199 st = util.fstat(fp)
213 st = util.fstat(fp)
200 if util.isowner(st):
214 if util.isowner(st):
201 return True
215 return True
202
216
203 tusers, tgroups = self._trustusers, self._trustgroups
217 tusers, tgroups = self._trustusers, self._trustgroups
204 if '*' in tusers or '*' in tgroups:
218 if '*' in tusers or '*' in tgroups:
205 return True
219 return True
206
220
207 user = util.username(st.st_uid)
221 user = util.username(st.st_uid)
208 group = util.groupname(st.st_gid)
222 group = util.groupname(st.st_gid)
209 if user in tusers or group in tgroups or user == util.username():
223 if user in tusers or group in tgroups or user == util.username():
210 return True
224 return True
211
225
212 if self._reportuntrusted:
226 if self._reportuntrusted:
213 self.warn(_('not trusting file %s from untrusted '
227 self.warn(_('not trusting file %s from untrusted '
214 'user %s, group %s\n') % (f, user, group))
228 'user %s, group %s\n') % (f, user, group))
215 return False
229 return False
216
230
217 def readconfig(self, filename, root=None, trust=False,
231 def readconfig(self, filename, root=None, trust=False,
218 sections=None, remap=None):
232 sections=None, remap=None):
219 try:
233 try:
220 fp = open(filename, u'rb')
234 fp = open(filename, u'rb')
221 except IOError:
235 except IOError:
222 if not sections: # ignore unless we were looking for something
236 if not sections: # ignore unless we were looking for something
223 return
237 return
224 raise
238 raise
225
239
226 cfg = config.config()
240 cfg = config.config()
227 trusted = sections or trust or self._trusted(fp, filename)
241 trusted = sections or trust or self._trusted(fp, filename)
228
242
229 try:
243 try:
230 cfg.read(filename, fp, sections=sections, remap=remap)
244 cfg.read(filename, fp, sections=sections, remap=remap)
231 fp.close()
245 fp.close()
232 except error.ConfigError as inst:
246 except error.ConfigError as inst:
233 if trusted:
247 if trusted:
234 raise
248 raise
235 self.warn(_("ignored: %s\n") % str(inst))
249 self.warn(_("ignored: %s\n") % str(inst))
236
250
237 if self.plain():
251 if self.plain():
238 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
252 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
239 'logtemplate', 'statuscopies', 'style',
253 'logtemplate', 'statuscopies', 'style',
240 'traceback', 'verbose'):
254 'traceback', 'verbose'):
241 if k in cfg['ui']:
255 if k in cfg['ui']:
242 del cfg['ui'][k]
256 del cfg['ui'][k]
243 for k, v in cfg.items('defaults'):
257 for k, v in cfg.items('defaults'):
244 del cfg['defaults'][k]
258 del cfg['defaults'][k]
245 # Don't remove aliases from the configuration if in the exceptionlist
259 # Don't remove aliases from the configuration if in the exceptionlist
246 if self.plain('alias'):
260 if self.plain('alias'):
247 for k, v in cfg.items('alias'):
261 for k, v in cfg.items('alias'):
248 del cfg['alias'][k]
262 del cfg['alias'][k]
249 if self.plain('revsetalias'):
263 if self.plain('revsetalias'):
250 for k, v in cfg.items('revsetalias'):
264 for k, v in cfg.items('revsetalias'):
251 del cfg['revsetalias'][k]
265 del cfg['revsetalias'][k]
252 if self.plain('templatealias'):
266 if self.plain('templatealias'):
253 for k, v in cfg.items('templatealias'):
267 for k, v in cfg.items('templatealias'):
254 del cfg['templatealias'][k]
268 del cfg['templatealias'][k]
255
269
256 if trusted:
270 if trusted:
257 self._tcfg.update(cfg)
271 self._tcfg.update(cfg)
258 self._tcfg.update(self._ocfg)
272 self._tcfg.update(self._ocfg)
259 self._ucfg.update(cfg)
273 self._ucfg.update(cfg)
260 self._ucfg.update(self._ocfg)
274 self._ucfg.update(self._ocfg)
261
275
262 if root is None:
276 if root is None:
263 root = os.path.expanduser('~')
277 root = os.path.expanduser('~')
264 self.fixconfig(root=root)
278 self.fixconfig(root=root)
265
279
266 def fixconfig(self, root=None, section=None):
280 def fixconfig(self, root=None, section=None):
267 if section in (None, 'paths'):
281 if section in (None, 'paths'):
268 # expand vars and ~
282 # expand vars and ~
269 # translate paths relative to root (or home) into absolute paths
283 # translate paths relative to root (or home) into absolute paths
270 root = root or pycompat.getcwd()
284 root = root or pycompat.getcwd()
271 for c in self._tcfg, self._ucfg, self._ocfg:
285 for c in self._tcfg, self._ucfg, self._ocfg:
272 for n, p in c.items('paths'):
286 for n, p in c.items('paths'):
273 # Ignore sub-options.
287 # Ignore sub-options.
274 if ':' in n:
288 if ':' in n:
275 continue
289 continue
276 if not p:
290 if not p:
277 continue
291 continue
278 if '%%' in p:
292 if '%%' in p:
279 s = self.configsource('paths', n) or 'none'
293 s = self.configsource('paths', n) or 'none'
280 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
294 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
281 % (n, p, s))
295 % (n, p, s))
282 p = p.replace('%%', '%')
296 p = p.replace('%%', '%')
283 p = util.expandpath(p)
297 p = util.expandpath(p)
284 if not util.hasscheme(p) and not os.path.isabs(p):
298 if not util.hasscheme(p) and not os.path.isabs(p):
285 p = os.path.normpath(os.path.join(root, p))
299 p = os.path.normpath(os.path.join(root, p))
286 c.set("paths", n, p)
300 c.set("paths", n, p)
287
301
288 if section in (None, 'ui'):
302 if section in (None, 'ui'):
289 # update ui options
303 # update ui options
290 self.debugflag = self.configbool('ui', 'debug')
304 self.debugflag = self.configbool('ui', 'debug')
291 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
305 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
292 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
306 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
293 if self.verbose and self.quiet:
307 if self.verbose and self.quiet:
294 self.quiet = self.verbose = False
308 self.quiet = self.verbose = False
295 self._reportuntrusted = self.debugflag or self.configbool("ui",
309 self._reportuntrusted = self.debugflag or self.configbool("ui",
296 "report_untrusted", True)
310 "report_untrusted", True)
297 self.tracebackflag = self.configbool('ui', 'traceback', False)
311 self.tracebackflag = self.configbool('ui', 'traceback', False)
312 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
298
313
299 if section in (None, 'trusted'):
314 if section in (None, 'trusted'):
300 # update trust information
315 # update trust information
301 self._trustusers.update(self.configlist('trusted', 'users'))
316 self._trustusers.update(self.configlist('trusted', 'users'))
302 self._trustgroups.update(self.configlist('trusted', 'groups'))
317 self._trustgroups.update(self.configlist('trusted', 'groups'))
303
318
304 def backupconfig(self, section, item):
319 def backupconfig(self, section, item):
305 return (self._ocfg.backup(section, item),
320 return (self._ocfg.backup(section, item),
306 self._tcfg.backup(section, item),
321 self._tcfg.backup(section, item),
307 self._ucfg.backup(section, item),)
322 self._ucfg.backup(section, item),)
308 def restoreconfig(self, data):
323 def restoreconfig(self, data):
309 self._ocfg.restore(data[0])
324 self._ocfg.restore(data[0])
310 self._tcfg.restore(data[1])
325 self._tcfg.restore(data[1])
311 self._ucfg.restore(data[2])
326 self._ucfg.restore(data[2])
312
327
313 def setconfig(self, section, name, value, source=''):
328 def setconfig(self, section, name, value, source=''):
314 for cfg in (self._ocfg, self._tcfg, self._ucfg):
329 for cfg in (self._ocfg, self._tcfg, self._ucfg):
315 cfg.set(section, name, value, source)
330 cfg.set(section, name, value, source)
316 self.fixconfig(section=section)
331 self.fixconfig(section=section)
317
332
318 def _data(self, untrusted):
333 def _data(self, untrusted):
319 return untrusted and self._ucfg or self._tcfg
334 return untrusted and self._ucfg or self._tcfg
320
335
321 def configsource(self, section, name, untrusted=False):
336 def configsource(self, section, name, untrusted=False):
322 return self._data(untrusted).source(section, name)
337 return self._data(untrusted).source(section, name)
323
338
324 def config(self, section, name, default=None, untrusted=False):
339 def config(self, section, name, default=None, untrusted=False):
325 if isinstance(name, list):
340 if isinstance(name, list):
326 alternates = name
341 alternates = name
327 else:
342 else:
328 alternates = [name]
343 alternates = [name]
329
344
330 for n in alternates:
345 for n in alternates:
331 value = self._data(untrusted).get(section, n, None)
346 value = self._data(untrusted).get(section, n, None)
332 if value is not None:
347 if value is not None:
333 name = n
348 name = n
334 break
349 break
335 else:
350 else:
336 value = default
351 value = default
337
352
338 if self.debugflag and not untrusted and self._reportuntrusted:
353 if self.debugflag and not untrusted and self._reportuntrusted:
339 for n in alternates:
354 for n in alternates:
340 uvalue = self._ucfg.get(section, n)
355 uvalue = self._ucfg.get(section, n)
341 if uvalue is not None and uvalue != value:
356 if uvalue is not None and uvalue != value:
342 self.debug("ignoring untrusted configuration option "
357 self.debug("ignoring untrusted configuration option "
343 "%s.%s = %s\n" % (section, n, uvalue))
358 "%s.%s = %s\n" % (section, n, uvalue))
344 return value
359 return value
345
360
346 def configsuboptions(self, section, name, default=None, untrusted=False):
361 def configsuboptions(self, section, name, default=None, untrusted=False):
347 """Get a config option and all sub-options.
362 """Get a config option and all sub-options.
348
363
349 Some config options have sub-options that are declared with the
364 Some config options have sub-options that are declared with the
350 format "key:opt = value". This method is used to return the main
365 format "key:opt = value". This method is used to return the main
351 option and all its declared sub-options.
366 option and all its declared sub-options.
352
367
353 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
368 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
354 is a dict of defined sub-options where keys and values are strings.
369 is a dict of defined sub-options where keys and values are strings.
355 """
370 """
356 data = self._data(untrusted)
371 data = self._data(untrusted)
357 main = data.get(section, name, default)
372 main = data.get(section, name, default)
358 if self.debugflag and not untrusted and self._reportuntrusted:
373 if self.debugflag and not untrusted and self._reportuntrusted:
359 uvalue = self._ucfg.get(section, name)
374 uvalue = self._ucfg.get(section, name)
360 if uvalue is not None and uvalue != main:
375 if uvalue is not None and uvalue != main:
361 self.debug('ignoring untrusted configuration option '
376 self.debug('ignoring untrusted configuration option '
362 '%s.%s = %s\n' % (section, name, uvalue))
377 '%s.%s = %s\n' % (section, name, uvalue))
363
378
364 sub = {}
379 sub = {}
365 prefix = '%s:' % name
380 prefix = '%s:' % name
366 for k, v in data.items(section):
381 for k, v in data.items(section):
367 if k.startswith(prefix):
382 if k.startswith(prefix):
368 sub[k[len(prefix):]] = v
383 sub[k[len(prefix):]] = v
369
384
370 if self.debugflag and not untrusted and self._reportuntrusted:
385 if self.debugflag and not untrusted and self._reportuntrusted:
371 for k, v in sub.items():
386 for k, v in sub.items():
372 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
387 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
373 if uvalue is not None and uvalue != v:
388 if uvalue is not None and uvalue != v:
374 self.debug('ignoring untrusted configuration option '
389 self.debug('ignoring untrusted configuration option '
375 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
390 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
376
391
377 return main, sub
392 return main, sub
378
393
379 def configpath(self, section, name, default=None, untrusted=False):
394 def configpath(self, section, name, default=None, untrusted=False):
380 'get a path config item, expanded relative to repo root or config file'
395 'get a path config item, expanded relative to repo root or config file'
381 v = self.config(section, name, default, untrusted)
396 v = self.config(section, name, default, untrusted)
382 if v is None:
397 if v is None:
383 return None
398 return None
384 if not os.path.isabs(v) or "://" not in v:
399 if not os.path.isabs(v) or "://" not in v:
385 src = self.configsource(section, name, untrusted)
400 src = self.configsource(section, name, untrusted)
386 if ':' in src:
401 if ':' in src:
387 base = os.path.dirname(src.rsplit(':')[0])
402 base = os.path.dirname(src.rsplit(':')[0])
388 v = os.path.join(base, os.path.expanduser(v))
403 v = os.path.join(base, os.path.expanduser(v))
389 return v
404 return v
390
405
391 def configbool(self, section, name, default=False, untrusted=False):
406 def configbool(self, section, name, default=False, untrusted=False):
392 """parse a configuration element as a boolean
407 """parse a configuration element as a boolean
393
408
394 >>> u = ui(); s = 'foo'
409 >>> u = ui(); s = 'foo'
395 >>> u.setconfig(s, 'true', 'yes')
410 >>> u.setconfig(s, 'true', 'yes')
396 >>> u.configbool(s, 'true')
411 >>> u.configbool(s, 'true')
397 True
412 True
398 >>> u.setconfig(s, 'false', 'no')
413 >>> u.setconfig(s, 'false', 'no')
399 >>> u.configbool(s, 'false')
414 >>> u.configbool(s, 'false')
400 False
415 False
401 >>> u.configbool(s, 'unknown')
416 >>> u.configbool(s, 'unknown')
402 False
417 False
403 >>> u.configbool(s, 'unknown', True)
418 >>> u.configbool(s, 'unknown', True)
404 True
419 True
405 >>> u.setconfig(s, 'invalid', 'somevalue')
420 >>> u.setconfig(s, 'invalid', 'somevalue')
406 >>> u.configbool(s, 'invalid')
421 >>> u.configbool(s, 'invalid')
407 Traceback (most recent call last):
422 Traceback (most recent call last):
408 ...
423 ...
409 ConfigError: foo.invalid is not a boolean ('somevalue')
424 ConfigError: foo.invalid is not a boolean ('somevalue')
410 """
425 """
411
426
412 v = self.config(section, name, None, untrusted)
427 v = self.config(section, name, None, untrusted)
413 if v is None:
428 if v is None:
414 return default
429 return default
415 if isinstance(v, bool):
430 if isinstance(v, bool):
416 return v
431 return v
417 b = util.parsebool(v)
432 b = util.parsebool(v)
418 if b is None:
433 if b is None:
419 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
434 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
420 % (section, name, v))
435 % (section, name, v))
421 return b
436 return b
422
437
423 def configwith(self, convert, section, name, default=None,
438 def configwith(self, convert, section, name, default=None,
424 desc=None, untrusted=False):
439 desc=None, untrusted=False):
425 """parse a configuration element with a conversion function
440 """parse a configuration element with a conversion function
426
441
427 >>> u = ui(); s = 'foo'
442 >>> u = ui(); s = 'foo'
428 >>> u.setconfig(s, 'float1', '42')
443 >>> u.setconfig(s, 'float1', '42')
429 >>> u.configwith(float, s, 'float1')
444 >>> u.configwith(float, s, 'float1')
430 42.0
445 42.0
431 >>> u.setconfig(s, 'float2', '-4.25')
446 >>> u.setconfig(s, 'float2', '-4.25')
432 >>> u.configwith(float, s, 'float2')
447 >>> u.configwith(float, s, 'float2')
433 -4.25
448 -4.25
434 >>> u.configwith(float, s, 'unknown', 7)
449 >>> u.configwith(float, s, 'unknown', 7)
435 7
450 7
436 >>> u.setconfig(s, 'invalid', 'somevalue')
451 >>> u.setconfig(s, 'invalid', 'somevalue')
437 >>> u.configwith(float, s, 'invalid')
452 >>> u.configwith(float, s, 'invalid')
438 Traceback (most recent call last):
453 Traceback (most recent call last):
439 ...
454 ...
440 ConfigError: foo.invalid is not a valid float ('somevalue')
455 ConfigError: foo.invalid is not a valid float ('somevalue')
441 >>> u.configwith(float, s, 'invalid', desc='womble')
456 >>> u.configwith(float, s, 'invalid', desc='womble')
442 Traceback (most recent call last):
457 Traceback (most recent call last):
443 ...
458 ...
444 ConfigError: foo.invalid is not a valid womble ('somevalue')
459 ConfigError: foo.invalid is not a valid womble ('somevalue')
445 """
460 """
446
461
447 v = self.config(section, name, None, untrusted)
462 v = self.config(section, name, None, untrusted)
448 if v is None:
463 if v is None:
449 return default
464 return default
450 try:
465 try:
451 return convert(v)
466 return convert(v)
452 except ValueError:
467 except ValueError:
453 if desc is None:
468 if desc is None:
454 desc = convert.__name__
469 desc = convert.__name__
455 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
470 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
456 % (section, name, desc, v))
471 % (section, name, desc, v))
457
472
458 def configint(self, section, name, default=None, untrusted=False):
473 def configint(self, section, name, default=None, untrusted=False):
459 """parse a configuration element as an integer
474 """parse a configuration element as an integer
460
475
461 >>> u = ui(); s = 'foo'
476 >>> u = ui(); s = 'foo'
462 >>> u.setconfig(s, 'int1', '42')
477 >>> u.setconfig(s, 'int1', '42')
463 >>> u.configint(s, 'int1')
478 >>> u.configint(s, 'int1')
464 42
479 42
465 >>> u.setconfig(s, 'int2', '-42')
480 >>> u.setconfig(s, 'int2', '-42')
466 >>> u.configint(s, 'int2')
481 >>> u.configint(s, 'int2')
467 -42
482 -42
468 >>> u.configint(s, 'unknown', 7)
483 >>> u.configint(s, 'unknown', 7)
469 7
484 7
470 >>> u.setconfig(s, 'invalid', 'somevalue')
485 >>> u.setconfig(s, 'invalid', 'somevalue')
471 >>> u.configint(s, 'invalid')
486 >>> u.configint(s, 'invalid')
472 Traceback (most recent call last):
487 Traceback (most recent call last):
473 ...
488 ...
474 ConfigError: foo.invalid is not a valid integer ('somevalue')
489 ConfigError: foo.invalid is not a valid integer ('somevalue')
475 """
490 """
476
491
477 return self.configwith(int, section, name, default, 'integer',
492 return self.configwith(int, section, name, default, 'integer',
478 untrusted)
493 untrusted)
479
494
480 def configbytes(self, section, name, default=0, untrusted=False):
495 def configbytes(self, section, name, default=0, untrusted=False):
481 """parse a configuration element as a quantity in bytes
496 """parse a configuration element as a quantity in bytes
482
497
483 Units can be specified as b (bytes), k or kb (kilobytes), m or
498 Units can be specified as b (bytes), k or kb (kilobytes), m or
484 mb (megabytes), g or gb (gigabytes).
499 mb (megabytes), g or gb (gigabytes).
485
500
486 >>> u = ui(); s = 'foo'
501 >>> u = ui(); s = 'foo'
487 >>> u.setconfig(s, 'val1', '42')
502 >>> u.setconfig(s, 'val1', '42')
488 >>> u.configbytes(s, 'val1')
503 >>> u.configbytes(s, 'val1')
489 42
504 42
490 >>> u.setconfig(s, 'val2', '42.5 kb')
505 >>> u.setconfig(s, 'val2', '42.5 kb')
491 >>> u.configbytes(s, 'val2')
506 >>> u.configbytes(s, 'val2')
492 43520
507 43520
493 >>> u.configbytes(s, 'unknown', '7 MB')
508 >>> u.configbytes(s, 'unknown', '7 MB')
494 7340032
509 7340032
495 >>> u.setconfig(s, 'invalid', 'somevalue')
510 >>> u.setconfig(s, 'invalid', 'somevalue')
496 >>> u.configbytes(s, 'invalid')
511 >>> u.configbytes(s, 'invalid')
497 Traceback (most recent call last):
512 Traceback (most recent call last):
498 ...
513 ...
499 ConfigError: foo.invalid is not a byte quantity ('somevalue')
514 ConfigError: foo.invalid is not a byte quantity ('somevalue')
500 """
515 """
501
516
502 value = self.config(section, name)
517 value = self.config(section, name)
503 if value is None:
518 if value is None:
504 if not isinstance(default, str):
519 if not isinstance(default, str):
505 return default
520 return default
506 value = default
521 value = default
507 try:
522 try:
508 return util.sizetoint(value)
523 return util.sizetoint(value)
509 except error.ParseError:
524 except error.ParseError:
510 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
525 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
511 % (section, name, value))
526 % (section, name, value))
512
527
513 def configlist(self, section, name, default=None, untrusted=False):
528 def configlist(self, section, name, default=None, untrusted=False):
514 """parse a configuration element as a list of comma/space separated
529 """parse a configuration element as a list of comma/space separated
515 strings
530 strings
516
531
517 >>> u = ui(); s = 'foo'
532 >>> u = ui(); s = 'foo'
518 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
533 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
519 >>> u.configlist(s, 'list1')
534 >>> u.configlist(s, 'list1')
520 ['this', 'is', 'a small', 'test']
535 ['this', 'is', 'a small', 'test']
521 """
536 """
522
537
523 def _parse_plain(parts, s, offset):
538 def _parse_plain(parts, s, offset):
524 whitespace = False
539 whitespace = False
525 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
540 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
526 whitespace = True
541 whitespace = True
527 offset += 1
542 offset += 1
528 if offset >= len(s):
543 if offset >= len(s):
529 return None, parts, offset
544 return None, parts, offset
530 if whitespace:
545 if whitespace:
531 parts.append('')
546 parts.append('')
532 if s[offset] == '"' and not parts[-1]:
547 if s[offset] == '"' and not parts[-1]:
533 return _parse_quote, parts, offset + 1
548 return _parse_quote, parts, offset + 1
534 elif s[offset] == '"' and parts[-1][-1] == '\\':
549 elif s[offset] == '"' and parts[-1][-1] == '\\':
535 parts[-1] = parts[-1][:-1] + s[offset]
550 parts[-1] = parts[-1][:-1] + s[offset]
536 return _parse_plain, parts, offset + 1
551 return _parse_plain, parts, offset + 1
537 parts[-1] += s[offset]
552 parts[-1] += s[offset]
538 return _parse_plain, parts, offset + 1
553 return _parse_plain, parts, offset + 1
539
554
540 def _parse_quote(parts, s, offset):
555 def _parse_quote(parts, s, offset):
541 if offset < len(s) and s[offset] == '"': # ""
556 if offset < len(s) and s[offset] == '"': # ""
542 parts.append('')
557 parts.append('')
543 offset += 1
558 offset += 1
544 while offset < len(s) and (s[offset].isspace() or
559 while offset < len(s) and (s[offset].isspace() or
545 s[offset] == ','):
560 s[offset] == ','):
546 offset += 1
561 offset += 1
547 return _parse_plain, parts, offset
562 return _parse_plain, parts, offset
548
563
549 while offset < len(s) and s[offset] != '"':
564 while offset < len(s) and s[offset] != '"':
550 if (s[offset] == '\\' and offset + 1 < len(s)
565 if (s[offset] == '\\' and offset + 1 < len(s)
551 and s[offset + 1] == '"'):
566 and s[offset + 1] == '"'):
552 offset += 1
567 offset += 1
553 parts[-1] += '"'
568 parts[-1] += '"'
554 else:
569 else:
555 parts[-1] += s[offset]
570 parts[-1] += s[offset]
556 offset += 1
571 offset += 1
557
572
558 if offset >= len(s):
573 if offset >= len(s):
559 real_parts = _configlist(parts[-1])
574 real_parts = _configlist(parts[-1])
560 if not real_parts:
575 if not real_parts:
561 parts[-1] = '"'
576 parts[-1] = '"'
562 else:
577 else:
563 real_parts[0] = '"' + real_parts[0]
578 real_parts[0] = '"' + real_parts[0]
564 parts = parts[:-1]
579 parts = parts[:-1]
565 parts.extend(real_parts)
580 parts.extend(real_parts)
566 return None, parts, offset
581 return None, parts, offset
567
582
568 offset += 1
583 offset += 1
569 while offset < len(s) and s[offset] in [' ', ',']:
584 while offset < len(s) and s[offset] in [' ', ',']:
570 offset += 1
585 offset += 1
571
586
572 if offset < len(s):
587 if offset < len(s):
573 if offset + 1 == len(s) and s[offset] == '"':
588 if offset + 1 == len(s) and s[offset] == '"':
574 parts[-1] += '"'
589 parts[-1] += '"'
575 offset += 1
590 offset += 1
576 else:
591 else:
577 parts.append('')
592 parts.append('')
578 else:
593 else:
579 return None, parts, offset
594 return None, parts, offset
580
595
581 return _parse_plain, parts, offset
596 return _parse_plain, parts, offset
582
597
583 def _configlist(s):
598 def _configlist(s):
584 s = s.rstrip(' ,')
599 s = s.rstrip(' ,')
585 if not s:
600 if not s:
586 return []
601 return []
587 parser, parts, offset = _parse_plain, [''], 0
602 parser, parts, offset = _parse_plain, [''], 0
588 while parser:
603 while parser:
589 parser, parts, offset = parser(parts, s, offset)
604 parser, parts, offset = parser(parts, s, offset)
590 return parts
605 return parts
591
606
592 result = self.config(section, name, untrusted=untrusted)
607 result = self.config(section, name, untrusted=untrusted)
593 if result is None:
608 if result is None:
594 result = default or []
609 result = default or []
595 if isinstance(result, bytes):
610 if isinstance(result, bytes):
596 result = _configlist(result.lstrip(' ,\n'))
611 result = _configlist(result.lstrip(' ,\n'))
597 if result is None:
612 if result is None:
598 result = default or []
613 result = default or []
599 return result
614 return result
600
615
601 def hasconfig(self, section, name, untrusted=False):
616 def hasconfig(self, section, name, untrusted=False):
602 return self._data(untrusted).hasitem(section, name)
617 return self._data(untrusted).hasitem(section, name)
603
618
604 def has_section(self, section, untrusted=False):
619 def has_section(self, section, untrusted=False):
605 '''tell whether section exists in config.'''
620 '''tell whether section exists in config.'''
606 return section in self._data(untrusted)
621 return section in self._data(untrusted)
607
622
608 def configitems(self, section, untrusted=False, ignoresub=False):
623 def configitems(self, section, untrusted=False, ignoresub=False):
609 items = self._data(untrusted).items(section)
624 items = self._data(untrusted).items(section)
610 if ignoresub:
625 if ignoresub:
611 newitems = {}
626 newitems = {}
612 for k, v in items:
627 for k, v in items:
613 if ':' not in k:
628 if ':' not in k:
614 newitems[k] = v
629 newitems[k] = v
615 items = newitems.items()
630 items = newitems.items()
616 if self.debugflag and not untrusted and self._reportuntrusted:
631 if self.debugflag and not untrusted and self._reportuntrusted:
617 for k, v in self._ucfg.items(section):
632 for k, v in self._ucfg.items(section):
618 if self._tcfg.get(section, k) != v:
633 if self._tcfg.get(section, k) != v:
619 self.debug("ignoring untrusted configuration option "
634 self.debug("ignoring untrusted configuration option "
620 "%s.%s = %s\n" % (section, k, v))
635 "%s.%s = %s\n" % (section, k, v))
621 return items
636 return items
622
637
623 def walkconfig(self, untrusted=False):
638 def walkconfig(self, untrusted=False):
624 cfg = self._data(untrusted)
639 cfg = self._data(untrusted)
625 for section in cfg.sections():
640 for section in cfg.sections():
626 for name, value in self.configitems(section, untrusted):
641 for name, value in self.configitems(section, untrusted):
627 yield section, name, value
642 yield section, name, value
628
643
629 def plain(self, feature=None):
644 def plain(self, feature=None):
630 '''is plain mode active?
645 '''is plain mode active?
631
646
632 Plain mode means that all configuration variables which affect
647 Plain mode means that all configuration variables which affect
633 the behavior and output of Mercurial should be
648 the behavior and output of Mercurial should be
634 ignored. Additionally, the output should be stable,
649 ignored. Additionally, the output should be stable,
635 reproducible and suitable for use in scripts or applications.
650 reproducible and suitable for use in scripts or applications.
636
651
637 The only way to trigger plain mode is by setting either the
652 The only way to trigger plain mode is by setting either the
638 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
653 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
639
654
640 The return value can either be
655 The return value can either be
641 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
656 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
642 - True otherwise
657 - True otherwise
643 '''
658 '''
644 if ('HGPLAIN' not in encoding.environ and
659 if ('HGPLAIN' not in encoding.environ and
645 'HGPLAINEXCEPT' not in encoding.environ):
660 'HGPLAINEXCEPT' not in encoding.environ):
646 return False
661 return False
647 exceptions = encoding.environ.get('HGPLAINEXCEPT',
662 exceptions = encoding.environ.get('HGPLAINEXCEPT',
648 '').strip().split(',')
663 '').strip().split(',')
649 if feature and exceptions:
664 if feature and exceptions:
650 return feature not in exceptions
665 return feature not in exceptions
651 return True
666 return True
652
667
653 def username(self):
668 def username(self):
654 """Return default username to be used in commits.
669 """Return default username to be used in commits.
655
670
656 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
671 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
657 and stop searching if one of these is set.
672 and stop searching if one of these is set.
658 If not found and ui.askusername is True, ask the user, else use
673 If not found and ui.askusername is True, ask the user, else use
659 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
674 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
660 """
675 """
661 user = encoding.environ.get("HGUSER")
676 user = encoding.environ.get("HGUSER")
662 if user is None:
677 if user is None:
663 user = self.config("ui", ["username", "user"])
678 user = self.config("ui", ["username", "user"])
664 if user is not None:
679 if user is not None:
665 user = os.path.expandvars(user)
680 user = os.path.expandvars(user)
666 if user is None:
681 if user is None:
667 user = encoding.environ.get("EMAIL")
682 user = encoding.environ.get("EMAIL")
668 if user is None and self.configbool("ui", "askusername"):
683 if user is None and self.configbool("ui", "askusername"):
669 user = self.prompt(_("enter a commit username:"), default=None)
684 user = self.prompt(_("enter a commit username:"), default=None)
670 if user is None and not self.interactive():
685 if user is None and not self.interactive():
671 try:
686 try:
672 user = '%s@%s' % (util.getuser(), socket.getfqdn())
687 user = '%s@%s' % (util.getuser(), socket.getfqdn())
673 self.warn(_("no username found, using '%s' instead\n") % user)
688 self.warn(_("no username found, using '%s' instead\n") % user)
674 except KeyError:
689 except KeyError:
675 pass
690 pass
676 if not user:
691 if not user:
677 raise error.Abort(_('no username supplied'),
692 raise error.Abort(_('no username supplied'),
678 hint=_("use 'hg config --edit' "
693 hint=_("use 'hg config --edit' "
679 'to set your username'))
694 'to set your username'))
680 if "\n" in user:
695 if "\n" in user:
681 raise error.Abort(_("username %s contains a newline\n")
696 raise error.Abort(_("username %s contains a newline\n")
682 % repr(user))
697 % repr(user))
683 return user
698 return user
684
699
685 def shortuser(self, user):
700 def shortuser(self, user):
686 """Return a short representation of a user name or email address."""
701 """Return a short representation of a user name or email address."""
687 if not self.verbose:
702 if not self.verbose:
688 user = util.shortuser(user)
703 user = util.shortuser(user)
689 return user
704 return user
690
705
691 def expandpath(self, loc, default=None):
706 def expandpath(self, loc, default=None):
692 """Return repository location relative to cwd or from [paths]"""
707 """Return repository location relative to cwd or from [paths]"""
693 try:
708 try:
694 p = self.paths.getpath(loc)
709 p = self.paths.getpath(loc)
695 if p:
710 if p:
696 return p.rawloc
711 return p.rawloc
697 except error.RepoError:
712 except error.RepoError:
698 pass
713 pass
699
714
700 if default:
715 if default:
701 try:
716 try:
702 p = self.paths.getpath(default)
717 p = self.paths.getpath(default)
703 if p:
718 if p:
704 return p.rawloc
719 return p.rawloc
705 except error.RepoError:
720 except error.RepoError:
706 pass
721 pass
707
722
708 return loc
723 return loc
709
724
710 @util.propertycache
725 @util.propertycache
711 def paths(self):
726 def paths(self):
712 return paths(self)
727 return paths(self)
713
728
714 def pushbuffer(self, error=False, subproc=False, labeled=False):
729 def pushbuffer(self, error=False, subproc=False, labeled=False):
715 """install a buffer to capture standard output of the ui object
730 """install a buffer to capture standard output of the ui object
716
731
717 If error is True, the error output will be captured too.
732 If error is True, the error output will be captured too.
718
733
719 If subproc is True, output from subprocesses (typically hooks) will be
734 If subproc is True, output from subprocesses (typically hooks) will be
720 captured too.
735 captured too.
721
736
722 If labeled is True, any labels associated with buffered
737 If labeled is True, any labels associated with buffered
723 output will be handled. By default, this has no effect
738 output will be handled. By default, this has no effect
724 on the output returned, but extensions and GUI tools may
739 on the output returned, but extensions and GUI tools may
725 handle this argument and returned styled output. If output
740 handle this argument and returned styled output. If output
726 is being buffered so it can be captured and parsed or
741 is being buffered so it can be captured and parsed or
727 processed, labeled should not be set to True.
742 processed, labeled should not be set to True.
728 """
743 """
729 self._buffers.append([])
744 self._buffers.append([])
730 self._bufferstates.append((error, subproc, labeled))
745 self._bufferstates.append((error, subproc, labeled))
731 self._bufferapplylabels = labeled
746 self._bufferapplylabels = labeled
732
747
733 def popbuffer(self):
748 def popbuffer(self):
734 '''pop the last buffer and return the buffered output'''
749 '''pop the last buffer and return the buffered output'''
735 self._bufferstates.pop()
750 self._bufferstates.pop()
736 if self._bufferstates:
751 if self._bufferstates:
737 self._bufferapplylabels = self._bufferstates[-1][2]
752 self._bufferapplylabels = self._bufferstates[-1][2]
738 else:
753 else:
739 self._bufferapplylabels = None
754 self._bufferapplylabels = None
740
755
741 return "".join(self._buffers.pop())
756 return "".join(self._buffers.pop())
742
757
743 def write(self, *args, **opts):
758 def write(self, *args, **opts):
744 '''write args to output
759 '''write args to output
745
760
746 By default, this method simply writes to the buffer or stdout,
761 By default, this method simply writes to the buffer or stdout,
747 but extensions or GUI tools may override this method,
762 but extensions or GUI tools may override this method,
748 write_err(), popbuffer(), and label() to style output from
763 write_err(), popbuffer(), and label() to style output from
749 various parts of hg.
764 various parts of hg.
750
765
751 An optional keyword argument, "label", can be passed in.
766 An optional keyword argument, "label", can be passed in.
752 This should be a string containing label names separated by
767 This should be a string containing label names separated by
753 space. Label names take the form of "topic.type". For example,
768 space. Label names take the form of "topic.type". For example,
754 ui.debug() issues a label of "ui.debug".
769 ui.debug() issues a label of "ui.debug".
755
770
756 When labeling output for a specific command, a label of
771 When labeling output for a specific command, a label of
757 "cmdname.type" is recommended. For example, status issues
772 "cmdname.type" is recommended. For example, status issues
758 a label of "status.modified" for modified files.
773 a label of "status.modified" for modified files.
759 '''
774 '''
760 if self._buffers and not opts.get('prompt', False):
775 if self._buffers and not opts.get('prompt', False):
761 self._buffers[-1].extend(a for a in args)
776 self._buffers[-1].extend(a for a in args)
762 else:
777 else:
763 self._progclear()
778 self._progclear()
764 for a in args:
779 for a in args:
765 self.fout.write(a)
780 self.fout.write(a)
766
781
767 def write_err(self, *args, **opts):
782 def write_err(self, *args, **opts):
768 self._progclear()
783 self._progclear()
769 try:
784 try:
770 if self._bufferstates and self._bufferstates[-1][0]:
785 if self._bufferstates and self._bufferstates[-1][0]:
771 return self.write(*args, **opts)
786 return self.write(*args, **opts)
772 if not getattr(self.fout, 'closed', False):
787 if not getattr(self.fout, 'closed', False):
773 self.fout.flush()
788 self.fout.flush()
774 for a in args:
789 for a in args:
775 self.ferr.write(a)
790 self.ferr.write(a)
776 # stderr may be buffered under win32 when redirected to files,
791 # stderr may be buffered under win32 when redirected to files,
777 # including stdout.
792 # including stdout.
778 if not getattr(self.ferr, 'closed', False):
793 if not getattr(self.ferr, 'closed', False):
779 self.ferr.flush()
794 self.ferr.flush()
780 except IOError as inst:
795 except IOError as inst:
781 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
796 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
782 raise
797 raise
783
798
784 def flush(self):
799 def flush(self):
785 try: self.fout.flush()
800 try: self.fout.flush()
786 except (IOError, ValueError): pass
801 except (IOError, ValueError): pass
787 try: self.ferr.flush()
802 try: self.ferr.flush()
788 except (IOError, ValueError): pass
803 except (IOError, ValueError): pass
789
804
790 def _isatty(self, fh):
805 def _isatty(self, fh):
791 if self.configbool('ui', 'nontty', False):
806 if self.configbool('ui', 'nontty', False):
792 return False
807 return False
793 return util.isatty(fh)
808 return util.isatty(fh)
794
809
795 def interface(self, feature):
810 def interface(self, feature):
796 """what interface to use for interactive console features?
811 """what interface to use for interactive console features?
797
812
798 The interface is controlled by the value of `ui.interface` but also by
813 The interface is controlled by the value of `ui.interface` but also by
799 the value of feature-specific configuration. For example:
814 the value of feature-specific configuration. For example:
800
815
801 ui.interface.histedit = text
816 ui.interface.histedit = text
802 ui.interface.chunkselector = curses
817 ui.interface.chunkselector = curses
803
818
804 Here the features are "histedit" and "chunkselector".
819 Here the features are "histedit" and "chunkselector".
805
820
806 The configuration above means that the default interfaces for commands
821 The configuration above means that the default interfaces for commands
807 is curses, the interface for histedit is text and the interface for
822 is curses, the interface for histedit is text and the interface for
808 selecting chunk is crecord (the best curses interface available).
823 selecting chunk is crecord (the best curses interface available).
809
824
810 Consider the following example:
825 Consider the following example:
811 ui.interface = curses
826 ui.interface = curses
812 ui.interface.histedit = text
827 ui.interface.histedit = text
813
828
814 Then histedit will use the text interface and chunkselector will use
829 Then histedit will use the text interface and chunkselector will use
815 the default curses interface (crecord at the moment).
830 the default curses interface (crecord at the moment).
816 """
831 """
817 alldefaults = frozenset(["text", "curses"])
832 alldefaults = frozenset(["text", "curses"])
818
833
819 featureinterfaces = {
834 featureinterfaces = {
820 "chunkselector": [
835 "chunkselector": [
821 "text",
836 "text",
822 "curses",
837 "curses",
823 ]
838 ]
824 }
839 }
825
840
826 # Feature-specific interface
841 # Feature-specific interface
827 if feature not in featureinterfaces.keys():
842 if feature not in featureinterfaces.keys():
828 # Programming error, not user error
843 # Programming error, not user error
829 raise ValueError("Unknown feature requested %s" % feature)
844 raise ValueError("Unknown feature requested %s" % feature)
830
845
831 availableinterfaces = frozenset(featureinterfaces[feature])
846 availableinterfaces = frozenset(featureinterfaces[feature])
832 if alldefaults > availableinterfaces:
847 if alldefaults > availableinterfaces:
833 # Programming error, not user error. We need a use case to
848 # Programming error, not user error. We need a use case to
834 # define the right thing to do here.
849 # define the right thing to do here.
835 raise ValueError(
850 raise ValueError(
836 "Feature %s does not handle all default interfaces" %
851 "Feature %s does not handle all default interfaces" %
837 feature)
852 feature)
838
853
839 if self.plain():
854 if self.plain():
840 return "text"
855 return "text"
841
856
842 # Default interface for all the features
857 # Default interface for all the features
843 defaultinterface = "text"
858 defaultinterface = "text"
844 i = self.config("ui", "interface", None)
859 i = self.config("ui", "interface", None)
845 if i in alldefaults:
860 if i in alldefaults:
846 defaultinterface = i
861 defaultinterface = i
847
862
848 choseninterface = defaultinterface
863 choseninterface = defaultinterface
849 f = self.config("ui", "interface.%s" % feature, None)
864 f = self.config("ui", "interface.%s" % feature, None)
850 if f in availableinterfaces:
865 if f in availableinterfaces:
851 choseninterface = f
866 choseninterface = f
852
867
853 if i is not None and defaultinterface != i:
868 if i is not None and defaultinterface != i:
854 if f is not None:
869 if f is not None:
855 self.warn(_("invalid value for ui.interface: %s\n") %
870 self.warn(_("invalid value for ui.interface: %s\n") %
856 (i,))
871 (i,))
857 else:
872 else:
858 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
873 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
859 (i, choseninterface))
874 (i, choseninterface))
860 if f is not None and choseninterface != f:
875 if f is not None and choseninterface != f:
861 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
876 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
862 (feature, f, choseninterface))
877 (feature, f, choseninterface))
863
878
864 return choseninterface
879 return choseninterface
865
880
866 def interactive(self):
881 def interactive(self):
867 '''is interactive input allowed?
882 '''is interactive input allowed?
868
883
869 An interactive session is a session where input can be reasonably read
884 An interactive session is a session where input can be reasonably read
870 from `sys.stdin'. If this function returns false, any attempt to read
885 from `sys.stdin'. If this function returns false, any attempt to read
871 from stdin should fail with an error, unless a sensible default has been
886 from stdin should fail with an error, unless a sensible default has been
872 specified.
887 specified.
873
888
874 Interactiveness is triggered by the value of the `ui.interactive'
889 Interactiveness is triggered by the value of the `ui.interactive'
875 configuration variable or - if it is unset - when `sys.stdin' points
890 configuration variable or - if it is unset - when `sys.stdin' points
876 to a terminal device.
891 to a terminal device.
877
892
878 This function refers to input only; for output, see `ui.formatted()'.
893 This function refers to input only; for output, see `ui.formatted()'.
879 '''
894 '''
880 i = self.configbool("ui", "interactive", None)
895 i = self.configbool("ui", "interactive", None)
881 if i is None:
896 if i is None:
882 # some environments replace stdin without implementing isatty
897 # some environments replace stdin without implementing isatty
883 # usually those are non-interactive
898 # usually those are non-interactive
884 return self._isatty(self.fin)
899 return self._isatty(self.fin)
885
900
886 return i
901 return i
887
902
888 def termwidth(self):
903 def termwidth(self):
889 '''how wide is the terminal in columns?
904 '''how wide is the terminal in columns?
890 '''
905 '''
891 if 'COLUMNS' in encoding.environ:
906 if 'COLUMNS' in encoding.environ:
892 try:
907 try:
893 return int(encoding.environ['COLUMNS'])
908 return int(encoding.environ['COLUMNS'])
894 except ValueError:
909 except ValueError:
895 pass
910 pass
896 return scmutil.termsize(self)[0]
911 return scmutil.termsize(self)[0]
897
912
898 def formatted(self):
913 def formatted(self):
899 '''should formatted output be used?
914 '''should formatted output be used?
900
915
901 It is often desirable to format the output to suite the output medium.
916 It is often desirable to format the output to suite the output medium.
902 Examples of this are truncating long lines or colorizing messages.
917 Examples of this are truncating long lines or colorizing messages.
903 However, this is not often not desirable when piping output into other
918 However, this is not often not desirable when piping output into other
904 utilities, e.g. `grep'.
919 utilities, e.g. `grep'.
905
920
906 Formatted output is triggered by the value of the `ui.formatted'
921 Formatted output is triggered by the value of the `ui.formatted'
907 configuration variable or - if it is unset - when `sys.stdout' points
922 configuration variable or - if it is unset - when `sys.stdout' points
908 to a terminal device. Please note that `ui.formatted' should be
923 to a terminal device. Please note that `ui.formatted' should be
909 considered an implementation detail; it is not intended for use outside
924 considered an implementation detail; it is not intended for use outside
910 Mercurial or its extensions.
925 Mercurial or its extensions.
911
926
912 This function refers to output only; for input, see `ui.interactive()'.
927 This function refers to output only; for input, see `ui.interactive()'.
913 This function always returns false when in plain mode, see `ui.plain()'.
928 This function always returns false when in plain mode, see `ui.plain()'.
914 '''
929 '''
915 if self.plain():
930 if self.plain():
916 return False
931 return False
917
932
918 i = self.configbool("ui", "formatted", None)
933 i = self.configbool("ui", "formatted", None)
919 if i is None:
934 if i is None:
920 # some environments replace stdout without implementing isatty
935 # some environments replace stdout without implementing isatty
921 # usually those are non-interactive
936 # usually those are non-interactive
922 return self._isatty(self.fout)
937 return self._isatty(self.fout)
923
938
924 return i
939 return i
925
940
926 def _readline(self, prompt=''):
941 def _readline(self, prompt=''):
927 if self._isatty(self.fin):
942 if self._isatty(self.fin):
928 try:
943 try:
929 # magically add command line editing support, where
944 # magically add command line editing support, where
930 # available
945 # available
931 import readline
946 import readline
932 # force demandimport to really load the module
947 # force demandimport to really load the module
933 readline.read_history_file
948 readline.read_history_file
934 # windows sometimes raises something other than ImportError
949 # windows sometimes raises something other than ImportError
935 except Exception:
950 except Exception:
936 pass
951 pass
937
952
938 # call write() so output goes through subclassed implementation
953 # call write() so output goes through subclassed implementation
939 # e.g. color extension on Windows
954 # e.g. color extension on Windows
940 self.write(prompt, prompt=True)
955 self.write(prompt, prompt=True)
941
956
942 # instead of trying to emulate raw_input, swap (self.fin,
957 # instead of trying to emulate raw_input, swap (self.fin,
943 # self.fout) with (sys.stdin, sys.stdout)
958 # self.fout) with (sys.stdin, sys.stdout)
944 oldin = sys.stdin
959 oldin = sys.stdin
945 oldout = sys.stdout
960 oldout = sys.stdout
946 sys.stdin = self.fin
961 sys.stdin = self.fin
947 sys.stdout = self.fout
962 sys.stdout = self.fout
948 # prompt ' ' must exist; otherwise readline may delete entire line
963 # prompt ' ' must exist; otherwise readline may delete entire line
949 # - http://bugs.python.org/issue12833
964 # - http://bugs.python.org/issue12833
950 line = raw_input(' ')
965 line = raw_input(' ')
951 sys.stdin = oldin
966 sys.stdin = oldin
952 sys.stdout = oldout
967 sys.stdout = oldout
953
968
954 # When stdin is in binary mode on Windows, it can cause
969 # When stdin is in binary mode on Windows, it can cause
955 # raw_input() to emit an extra trailing carriage return
970 # raw_input() to emit an extra trailing carriage return
956 if os.linesep == '\r\n' and line and line[-1] == '\r':
971 if os.linesep == '\r\n' and line and line[-1] == '\r':
957 line = line[:-1]
972 line = line[:-1]
958 return line
973 return line
959
974
960 def prompt(self, msg, default="y"):
975 def prompt(self, msg, default="y"):
961 """Prompt user with msg, read response.
976 """Prompt user with msg, read response.
962 If ui is not interactive, the default is returned.
977 If ui is not interactive, the default is returned.
963 """
978 """
964 if not self.interactive():
979 if not self.interactive():
965 self.write(msg, ' ', default or '', "\n")
980 self.write(msg, ' ', default or '', "\n")
966 return default
981 return default
967 try:
982 try:
968 r = self._readline(self.label(msg, 'ui.prompt'))
983 r = self._readline(self.label(msg, 'ui.prompt'))
969 if not r:
984 if not r:
970 r = default
985 r = default
971 if self.configbool('ui', 'promptecho'):
986 if self.configbool('ui', 'promptecho'):
972 self.write(r, "\n")
987 self.write(r, "\n")
973 return r
988 return r
974 except EOFError:
989 except EOFError:
975 raise error.ResponseExpected()
990 raise error.ResponseExpected()
976
991
977 @staticmethod
992 @staticmethod
978 def extractchoices(prompt):
993 def extractchoices(prompt):
979 """Extract prompt message and list of choices from specified prompt.
994 """Extract prompt message and list of choices from specified prompt.
980
995
981 This returns tuple "(message, choices)", and "choices" is the
996 This returns tuple "(message, choices)", and "choices" is the
982 list of tuple "(response character, text without &)".
997 list of tuple "(response character, text without &)".
983
998
984 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
999 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
985 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1000 ('awake? ', [('y', 'Yes'), ('n', 'No')])
986 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1001 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
987 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1002 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
988 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1003 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
989 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1004 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
990 """
1005 """
991
1006
992 # Sadly, the prompt string may have been built with a filename
1007 # Sadly, the prompt string may have been built with a filename
993 # containing "$$" so let's try to find the first valid-looking
1008 # containing "$$" so let's try to find the first valid-looking
994 # prompt to start parsing. Sadly, we also can't rely on
1009 # prompt to start parsing. Sadly, we also can't rely on
995 # choices containing spaces, ASCII, or basically anything
1010 # choices containing spaces, ASCII, or basically anything
996 # except an ampersand followed by a character.
1011 # except an ampersand followed by a character.
997 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1012 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
998 msg = m.group(1)
1013 msg = m.group(1)
999 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1014 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1000 return (msg,
1015 return (msg,
1001 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1016 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1002 for s in choices])
1017 for s in choices])
1003
1018
1004 def promptchoice(self, prompt, default=0):
1019 def promptchoice(self, prompt, default=0):
1005 """Prompt user with a message, read response, and ensure it matches
1020 """Prompt user with a message, read response, and ensure it matches
1006 one of the provided choices. The prompt is formatted as follows:
1021 one of the provided choices. The prompt is formatted as follows:
1007
1022
1008 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1023 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1009
1024
1010 The index of the choice is returned. Responses are case
1025 The index of the choice is returned. Responses are case
1011 insensitive. If ui is not interactive, the default is
1026 insensitive. If ui is not interactive, the default is
1012 returned.
1027 returned.
1013 """
1028 """
1014
1029
1015 msg, choices = self.extractchoices(prompt)
1030 msg, choices = self.extractchoices(prompt)
1016 resps = [r for r, t in choices]
1031 resps = [r for r, t in choices]
1017 while True:
1032 while True:
1018 r = self.prompt(msg, resps[default])
1033 r = self.prompt(msg, resps[default])
1019 if r.lower() in resps:
1034 if r.lower() in resps:
1020 return resps.index(r.lower())
1035 return resps.index(r.lower())
1021 self.write(_("unrecognized response\n"))
1036 self.write(_("unrecognized response\n"))
1022
1037
1023 def getpass(self, prompt=None, default=None):
1038 def getpass(self, prompt=None, default=None):
1024 if not self.interactive():
1039 if not self.interactive():
1025 return default
1040 return default
1026 try:
1041 try:
1027 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1042 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1028 # disable getpass() only if explicitly specified. it's still valid
1043 # disable getpass() only if explicitly specified. it's still valid
1029 # to interact with tty even if fin is not a tty.
1044 # to interact with tty even if fin is not a tty.
1030 if self.configbool('ui', 'nontty'):
1045 if self.configbool('ui', 'nontty'):
1031 l = self.fin.readline()
1046 l = self.fin.readline()
1032 if not l:
1047 if not l:
1033 raise EOFError
1048 raise EOFError
1034 return l.rstrip('\n')
1049 return l.rstrip('\n')
1035 else:
1050 else:
1036 return getpass.getpass('')
1051 return getpass.getpass('')
1037 except EOFError:
1052 except EOFError:
1038 raise error.ResponseExpected()
1053 raise error.ResponseExpected()
1039 def status(self, *msg, **opts):
1054 def status(self, *msg, **opts):
1040 '''write status message to output (if ui.quiet is False)
1055 '''write status message to output (if ui.quiet is False)
1041
1056
1042 This adds an output label of "ui.status".
1057 This adds an output label of "ui.status".
1043 '''
1058 '''
1044 if not self.quiet:
1059 if not self.quiet:
1045 opts['label'] = opts.get('label', '') + ' ui.status'
1060 opts['label'] = opts.get('label', '') + ' ui.status'
1046 self.write(*msg, **opts)
1061 self.write(*msg, **opts)
1047 def warn(self, *msg, **opts):
1062 def warn(self, *msg, **opts):
1048 '''write warning message to output (stderr)
1063 '''write warning message to output (stderr)
1049
1064
1050 This adds an output label of "ui.warning".
1065 This adds an output label of "ui.warning".
1051 '''
1066 '''
1052 opts['label'] = opts.get('label', '') + ' ui.warning'
1067 opts['label'] = opts.get('label', '') + ' ui.warning'
1053 self.write_err(*msg, **opts)
1068 self.write_err(*msg, **opts)
1054 def note(self, *msg, **opts):
1069 def note(self, *msg, **opts):
1055 '''write note to output (if ui.verbose is True)
1070 '''write note to output (if ui.verbose is True)
1056
1071
1057 This adds an output label of "ui.note".
1072 This adds an output label of "ui.note".
1058 '''
1073 '''
1059 if self.verbose:
1074 if self.verbose:
1060 opts['label'] = opts.get('label', '') + ' ui.note'
1075 opts['label'] = opts.get('label', '') + ' ui.note'
1061 self.write(*msg, **opts)
1076 self.write(*msg, **opts)
1062 def debug(self, *msg, **opts):
1077 def debug(self, *msg, **opts):
1063 '''write debug message to output (if ui.debugflag is True)
1078 '''write debug message to output (if ui.debugflag is True)
1064
1079
1065 This adds an output label of "ui.debug".
1080 This adds an output label of "ui.debug".
1066 '''
1081 '''
1067 if self.debugflag:
1082 if self.debugflag:
1068 opts['label'] = opts.get('label', '') + ' ui.debug'
1083 opts['label'] = opts.get('label', '') + ' ui.debug'
1069 self.write(*msg, **opts)
1084 self.write(*msg, **opts)
1070
1085
1071 def edit(self, text, user, extra=None, editform=None, pending=None,
1086 def edit(self, text, user, extra=None, editform=None, pending=None,
1072 repopath=None):
1087 repopath=None):
1073 extra_defaults = {
1088 extra_defaults = {
1074 'prefix': 'editor',
1089 'prefix': 'editor',
1075 'suffix': '.txt',
1090 'suffix': '.txt',
1076 }
1091 }
1077 if extra is not None:
1092 if extra is not None:
1078 extra_defaults.update(extra)
1093 extra_defaults.update(extra)
1079 extra = extra_defaults
1094 extra = extra_defaults
1080
1095
1081 rdir = None
1096 rdir = None
1082 if self.configbool('experimental', 'editortmpinhg'):
1097 if self.configbool('experimental', 'editortmpinhg'):
1083 rdir = repopath
1098 rdir = repopath
1084 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1099 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1085 suffix=extra['suffix'], text=True,
1100 suffix=extra['suffix'], text=True,
1086 dir=rdir)
1101 dir=rdir)
1087 try:
1102 try:
1088 f = os.fdopen(fd, pycompat.sysstr("w"))
1103 f = os.fdopen(fd, pycompat.sysstr("w"))
1089 f.write(text)
1104 f.write(text)
1090 f.close()
1105 f.close()
1091
1106
1092 environ = {'HGUSER': user}
1107 environ = {'HGUSER': user}
1093 if 'transplant_source' in extra:
1108 if 'transplant_source' in extra:
1094 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1109 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1095 for label in ('intermediate-source', 'source', 'rebase_source'):
1110 for label in ('intermediate-source', 'source', 'rebase_source'):
1096 if label in extra:
1111 if label in extra:
1097 environ.update({'HGREVISION': extra[label]})
1112 environ.update({'HGREVISION': extra[label]})
1098 break
1113 break
1099 if editform:
1114 if editform:
1100 environ.update({'HGEDITFORM': editform})
1115 environ.update({'HGEDITFORM': editform})
1101 if pending:
1116 if pending:
1102 environ.update({'HG_PENDING': pending})
1117 environ.update({'HG_PENDING': pending})
1103
1118
1104 editor = self.geteditor()
1119 editor = self.geteditor()
1105
1120
1106 self.system("%s \"%s\"" % (editor, name),
1121 self.system("%s \"%s\"" % (editor, name),
1107 environ=environ,
1122 environ=environ,
1108 onerr=error.Abort, errprefix=_("edit failed"))
1123 onerr=error.Abort, errprefix=_("edit failed"))
1109
1124
1110 f = open(name)
1125 f = open(name)
1111 t = f.read()
1126 t = f.read()
1112 f.close()
1127 f.close()
1113 finally:
1128 finally:
1114 os.unlink(name)
1129 os.unlink(name)
1115
1130
1116 return t
1131 return t
1117
1132
1118 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
1133 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
1119 '''execute shell command with appropriate output stream. command
1134 '''execute shell command with appropriate output stream. command
1120 output will be redirected if fout is not stdout.
1135 output will be redirected if fout is not stdout.
1121 '''
1136 '''
1122 out = self.fout
1137 out = self.fout
1123 if any(s[1] for s in self._bufferstates):
1138 if any(s[1] for s in self._bufferstates):
1124 out = self
1139 out = self
1125 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1140 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1126 errprefix=errprefix, out=out)
1141 errprefix=errprefix, out=out)
1127
1142
1128 def traceback(self, exc=None, force=False):
1143 def traceback(self, exc=None, force=False):
1129 '''print exception traceback if traceback printing enabled or forced.
1144 '''print exception traceback if traceback printing enabled or forced.
1130 only to call in exception handler. returns true if traceback
1145 only to call in exception handler. returns true if traceback
1131 printed.'''
1146 printed.'''
1132 if self.tracebackflag or force:
1147 if self.tracebackflag or force:
1133 if exc is None:
1148 if exc is None:
1134 exc = sys.exc_info()
1149 exc = sys.exc_info()
1135 cause = getattr(exc[1], 'cause', None)
1150 cause = getattr(exc[1], 'cause', None)
1136
1151
1137 if cause is not None:
1152 if cause is not None:
1138 causetb = traceback.format_tb(cause[2])
1153 causetb = traceback.format_tb(cause[2])
1139 exctb = traceback.format_tb(exc[2])
1154 exctb = traceback.format_tb(exc[2])
1140 exconly = traceback.format_exception_only(cause[0], cause[1])
1155 exconly = traceback.format_exception_only(cause[0], cause[1])
1141
1156
1142 # exclude frame where 'exc' was chained and rethrown from exctb
1157 # exclude frame where 'exc' was chained and rethrown from exctb
1143 self.write_err('Traceback (most recent call last):\n',
1158 self.write_err('Traceback (most recent call last):\n',
1144 ''.join(exctb[:-1]),
1159 ''.join(exctb[:-1]),
1145 ''.join(causetb),
1160 ''.join(causetb),
1146 ''.join(exconly))
1161 ''.join(exconly))
1147 else:
1162 else:
1148 output = traceback.format_exception(exc[0], exc[1], exc[2])
1163 output = traceback.format_exception(exc[0], exc[1], exc[2])
1149 self.write_err(''.join(output))
1164 self.write_err(''.join(output))
1150 return self.tracebackflag or force
1165 return self.tracebackflag or force
1151
1166
1152 def geteditor(self):
1167 def geteditor(self):
1153 '''return editor to use'''
1168 '''return editor to use'''
1154 if pycompat.sysplatform == 'plan9':
1169 if pycompat.sysplatform == 'plan9':
1155 # vi is the MIPS instruction simulator on Plan 9. We
1170 # vi is the MIPS instruction simulator on Plan 9. We
1156 # instead default to E to plumb commit messages to
1171 # instead default to E to plumb commit messages to
1157 # avoid confusion.
1172 # avoid confusion.
1158 editor = 'E'
1173 editor = 'E'
1159 else:
1174 else:
1160 editor = 'vi'
1175 editor = 'vi'
1161 return (encoding.environ.get("HGEDITOR") or
1176 return (encoding.environ.get("HGEDITOR") or
1162 self.config("ui", "editor") or
1177 self.config("ui", "editor") or
1163 encoding.environ.get("VISUAL") or
1178 encoding.environ.get("VISUAL") or
1164 encoding.environ.get("EDITOR", editor))
1179 encoding.environ.get("EDITOR", editor))
1165
1180
1166 @util.propertycache
1181 @util.propertycache
1167 def _progbar(self):
1182 def _progbar(self):
1168 """setup the progbar singleton to the ui object"""
1183 """setup the progbar singleton to the ui object"""
1169 if (self.quiet or self.debugflag
1184 if (self.quiet or self.debugflag
1170 or self.configbool('progress', 'disable', False)
1185 or self.configbool('progress', 'disable', False)
1171 or not progress.shouldprint(self)):
1186 or not progress.shouldprint(self)):
1172 return None
1187 return None
1173 return getprogbar(self)
1188 return getprogbar(self)
1174
1189
1175 def _progclear(self):
1190 def _progclear(self):
1176 """clear progress bar output if any. use it before any output"""
1191 """clear progress bar output if any. use it before any output"""
1177 if '_progbar' not in vars(self): # nothing loaded yet
1192 if '_progbar' not in vars(self): # nothing loaded yet
1178 return
1193 return
1179 if self._progbar is not None and self._progbar.printed:
1194 if self._progbar is not None and self._progbar.printed:
1180 self._progbar.clear()
1195 self._progbar.clear()
1181
1196
1182 def progress(self, topic, pos, item="", unit="", total=None):
1197 def progress(self, topic, pos, item="", unit="", total=None):
1183 '''show a progress message
1198 '''show a progress message
1184
1199
1185 By default a textual progress bar will be displayed if an operation
1200 By default a textual progress bar will be displayed if an operation
1186 takes too long. 'topic' is the current operation, 'item' is a
1201 takes too long. 'topic' is the current operation, 'item' is a
1187 non-numeric marker of the current position (i.e. the currently
1202 non-numeric marker of the current position (i.e. the currently
1188 in-process file), 'pos' is the current numeric position (i.e.
1203 in-process file), 'pos' is the current numeric position (i.e.
1189 revision, bytes, etc.), unit is a corresponding unit label,
1204 revision, bytes, etc.), unit is a corresponding unit label,
1190 and total is the highest expected pos.
1205 and total is the highest expected pos.
1191
1206
1192 Multiple nested topics may be active at a time.
1207 Multiple nested topics may be active at a time.
1193
1208
1194 All topics should be marked closed by setting pos to None at
1209 All topics should be marked closed by setting pos to None at
1195 termination.
1210 termination.
1196 '''
1211 '''
1197 if self._progbar is not None:
1212 if self._progbar is not None:
1198 self._progbar.progress(topic, pos, item=item, unit=unit,
1213 self._progbar.progress(topic, pos, item=item, unit=unit,
1199 total=total)
1214 total=total)
1200 if pos is None or not self.configbool('progress', 'debug'):
1215 if pos is None or not self.configbool('progress', 'debug'):
1201 return
1216 return
1202
1217
1203 if unit:
1218 if unit:
1204 unit = ' ' + unit
1219 unit = ' ' + unit
1205 if item:
1220 if item:
1206 item = ' ' + item
1221 item = ' ' + item
1207
1222
1208 if total:
1223 if total:
1209 pct = 100.0 * pos / total
1224 pct = 100.0 * pos / total
1210 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1225 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1211 % (topic, item, pos, total, unit, pct))
1226 % (topic, item, pos, total, unit, pct))
1212 else:
1227 else:
1213 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1228 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1214
1229
1215 def log(self, service, *msg, **opts):
1230 def log(self, service, *msg, **opts):
1216 '''hook for logging facility extensions
1231 '''hook for logging facility extensions
1217
1232
1218 service should be a readily-identifiable subsystem, which will
1233 service should be a readily-identifiable subsystem, which will
1219 allow filtering.
1234 allow filtering.
1220
1235
1221 *msg should be a newline-terminated format string to log, and
1236 *msg should be a newline-terminated format string to log, and
1222 then any values to %-format into that format string.
1237 then any values to %-format into that format string.
1223
1238
1224 **opts currently has no defined meanings.
1239 **opts currently has no defined meanings.
1225 '''
1240 '''
1226
1241
1227 def label(self, msg, label):
1242 def label(self, msg, label):
1228 '''style msg based on supplied label
1243 '''style msg based on supplied label
1229
1244
1230 Like ui.write(), this just returns msg unchanged, but extensions
1245 Like ui.write(), this just returns msg unchanged, but extensions
1231 and GUI tools can override it to allow styling output without
1246 and GUI tools can override it to allow styling output without
1232 writing it.
1247 writing it.
1233
1248
1234 ui.write(s, 'label') is equivalent to
1249 ui.write(s, 'label') is equivalent to
1235 ui.write(ui.label(s, 'label')).
1250 ui.write(ui.label(s, 'label')).
1236 '''
1251 '''
1237 return msg
1252 return msg
1238
1253
1239 def develwarn(self, msg, stacklevel=1, config=None):
1254 def develwarn(self, msg, stacklevel=1, config=None):
1240 """issue a developer warning message
1255 """issue a developer warning message
1241
1256
1242 Use 'stacklevel' to report the offender some layers further up in the
1257 Use 'stacklevel' to report the offender some layers further up in the
1243 stack.
1258 stack.
1244 """
1259 """
1245 if not self.configbool('devel', 'all-warnings'):
1260 if not self.configbool('devel', 'all-warnings'):
1246 if config is not None and not self.configbool('devel', config):
1261 if config is not None and not self.configbool('devel', config):
1247 return
1262 return
1248 msg = 'devel-warn: ' + msg
1263 msg = 'devel-warn: ' + msg
1249 stacklevel += 1 # get in develwarn
1264 stacklevel += 1 # get in develwarn
1250 if self.tracebackflag:
1265 if self.tracebackflag:
1251 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1266 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1252 self.log('develwarn', '%s at:\n%s' %
1267 self.log('develwarn', '%s at:\n%s' %
1253 (msg, ''.join(util.getstackframes(stacklevel))))
1268 (msg, ''.join(util.getstackframes(stacklevel))))
1254 else:
1269 else:
1255 curframe = inspect.currentframe()
1270 curframe = inspect.currentframe()
1256 calframe = inspect.getouterframes(curframe, 2)
1271 calframe = inspect.getouterframes(curframe, 2)
1257 self.write_err('%s at: %s:%s (%s)\n'
1272 self.write_err('%s at: %s:%s (%s)\n'
1258 % ((msg,) + calframe[stacklevel][1:4]))
1273 % ((msg,) + calframe[stacklevel][1:4]))
1259 self.log('develwarn', '%s at: %s:%s (%s)\n',
1274 self.log('develwarn', '%s at: %s:%s (%s)\n',
1260 msg, *calframe[stacklevel][1:4])
1275 msg, *calframe[stacklevel][1:4])
1261 curframe = calframe = None # avoid cycles
1276 curframe = calframe = None # avoid cycles
1262
1277
1263 def deprecwarn(self, msg, version):
1278 def deprecwarn(self, msg, version):
1264 """issue a deprecation warning
1279 """issue a deprecation warning
1265
1280
1266 - msg: message explaining what is deprecated and how to upgrade,
1281 - msg: message explaining what is deprecated and how to upgrade,
1267 - version: last version where the API will be supported,
1282 - version: last version where the API will be supported,
1268 """
1283 """
1269 if not (self.configbool('devel', 'all-warnings')
1284 if not (self.configbool('devel', 'all-warnings')
1270 or self.configbool('devel', 'deprec-warn')):
1285 or self.configbool('devel', 'deprec-warn')):
1271 return
1286 return
1272 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1287 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1273 " update your code.)") % version
1288 " update your code.)") % version
1274 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1289 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1275
1290
1276 def exportableenviron(self):
1291 def exportableenviron(self):
1277 """The environment variables that are safe to export, e.g. through
1292 """The environment variables that are safe to export, e.g. through
1278 hgweb.
1293 hgweb.
1279 """
1294 """
1280 return self._exportableenviron
1295 return self._exportableenviron
1281
1296
1282 @contextlib.contextmanager
1297 @contextlib.contextmanager
1283 def configoverride(self, overrides, source=""):
1298 def configoverride(self, overrides, source=""):
1284 """Context manager for temporary config overrides
1299 """Context manager for temporary config overrides
1285 `overrides` must be a dict of the following structure:
1300 `overrides` must be a dict of the following structure:
1286 {(section, name) : value}"""
1301 {(section, name) : value}"""
1287 backups = {}
1302 backups = {}
1288 try:
1303 try:
1289 for (section, name), value in overrides.items():
1304 for (section, name), value in overrides.items():
1290 backups[(section, name)] = self.backupconfig(section, name)
1305 backups[(section, name)] = self.backupconfig(section, name)
1291 self.setconfig(section, name, value, source)
1306 self.setconfig(section, name, value, source)
1292 yield
1307 yield
1293 finally:
1308 finally:
1294 for __, backup in backups.items():
1309 for __, backup in backups.items():
1295 self.restoreconfig(backup)
1310 self.restoreconfig(backup)
1296 # just restoring ui.quiet config to the previous value is not enough
1311 # just restoring ui.quiet config to the previous value is not enough
1297 # as it does not update ui.quiet class member
1312 # as it does not update ui.quiet class member
1298 if ('ui', 'quiet') in overrides:
1313 if ('ui', 'quiet') in overrides:
1299 self.fixconfig(section='ui')
1314 self.fixconfig(section='ui')
1300
1315
1301 class paths(dict):
1316 class paths(dict):
1302 """Represents a collection of paths and their configs.
1317 """Represents a collection of paths and their configs.
1303
1318
1304 Data is initially derived from ui instances and the config files they have
1319 Data is initially derived from ui instances and the config files they have
1305 loaded.
1320 loaded.
1306 """
1321 """
1307 def __init__(self, ui):
1322 def __init__(self, ui):
1308 dict.__init__(self)
1323 dict.__init__(self)
1309
1324
1310 for name, loc in ui.configitems('paths', ignoresub=True):
1325 for name, loc in ui.configitems('paths', ignoresub=True):
1311 # No location is the same as not existing.
1326 # No location is the same as not existing.
1312 if not loc:
1327 if not loc:
1313 continue
1328 continue
1314 loc, sub = ui.configsuboptions('paths', name)
1329 loc, sub = ui.configsuboptions('paths', name)
1315 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1330 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1316
1331
1317 def getpath(self, name, default=None):
1332 def getpath(self, name, default=None):
1318 """Return a ``path`` from a string, falling back to default.
1333 """Return a ``path`` from a string, falling back to default.
1319
1334
1320 ``name`` can be a named path or locations. Locations are filesystem
1335 ``name`` can be a named path or locations. Locations are filesystem
1321 paths or URIs.
1336 paths or URIs.
1322
1337
1323 Returns None if ``name`` is not a registered path, a URI, or a local
1338 Returns None if ``name`` is not a registered path, a URI, or a local
1324 path to a repo.
1339 path to a repo.
1325 """
1340 """
1326 # Only fall back to default if no path was requested.
1341 # Only fall back to default if no path was requested.
1327 if name is None:
1342 if name is None:
1328 if not default:
1343 if not default:
1329 default = ()
1344 default = ()
1330 elif not isinstance(default, (tuple, list)):
1345 elif not isinstance(default, (tuple, list)):
1331 default = (default,)
1346 default = (default,)
1332 for k in default:
1347 for k in default:
1333 try:
1348 try:
1334 return self[k]
1349 return self[k]
1335 except KeyError:
1350 except KeyError:
1336 continue
1351 continue
1337 return None
1352 return None
1338
1353
1339 # Most likely empty string.
1354 # Most likely empty string.
1340 # This may need to raise in the future.
1355 # This may need to raise in the future.
1341 if not name:
1356 if not name:
1342 return None
1357 return None
1343
1358
1344 try:
1359 try:
1345 return self[name]
1360 return self[name]
1346 except KeyError:
1361 except KeyError:
1347 # Try to resolve as a local path or URI.
1362 # Try to resolve as a local path or URI.
1348 try:
1363 try:
1349 # We don't pass sub-options in, so no need to pass ui instance.
1364 # We don't pass sub-options in, so no need to pass ui instance.
1350 return path(None, None, rawloc=name)
1365 return path(None, None, rawloc=name)
1351 except ValueError:
1366 except ValueError:
1352 raise error.RepoError(_('repository %s does not exist') %
1367 raise error.RepoError(_('repository %s does not exist') %
1353 name)
1368 name)
1354
1369
1355 _pathsuboptions = {}
1370 _pathsuboptions = {}
1356
1371
1357 def pathsuboption(option, attr):
1372 def pathsuboption(option, attr):
1358 """Decorator used to declare a path sub-option.
1373 """Decorator used to declare a path sub-option.
1359
1374
1360 Arguments are the sub-option name and the attribute it should set on
1375 Arguments are the sub-option name and the attribute it should set on
1361 ``path`` instances.
1376 ``path`` instances.
1362
1377
1363 The decorated function will receive as arguments a ``ui`` instance,
1378 The decorated function will receive as arguments a ``ui`` instance,
1364 ``path`` instance, and the string value of this option from the config.
1379 ``path`` instance, and the string value of this option from the config.
1365 The function should return the value that will be set on the ``path``
1380 The function should return the value that will be set on the ``path``
1366 instance.
1381 instance.
1367
1382
1368 This decorator can be used to perform additional verification of
1383 This decorator can be used to perform additional verification of
1369 sub-options and to change the type of sub-options.
1384 sub-options and to change the type of sub-options.
1370 """
1385 """
1371 def register(func):
1386 def register(func):
1372 _pathsuboptions[option] = (attr, func)
1387 _pathsuboptions[option] = (attr, func)
1373 return func
1388 return func
1374 return register
1389 return register
1375
1390
1376 @pathsuboption('pushurl', 'pushloc')
1391 @pathsuboption('pushurl', 'pushloc')
1377 def pushurlpathoption(ui, path, value):
1392 def pushurlpathoption(ui, path, value):
1378 u = util.url(value)
1393 u = util.url(value)
1379 # Actually require a URL.
1394 # Actually require a URL.
1380 if not u.scheme:
1395 if not u.scheme:
1381 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1396 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1382 return None
1397 return None
1383
1398
1384 # Don't support the #foo syntax in the push URL to declare branch to
1399 # Don't support the #foo syntax in the push URL to declare branch to
1385 # push.
1400 # push.
1386 if u.fragment:
1401 if u.fragment:
1387 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1402 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1388 'ignoring)\n') % path.name)
1403 'ignoring)\n') % path.name)
1389 u.fragment = None
1404 u.fragment = None
1390
1405
1391 return str(u)
1406 return str(u)
1392
1407
1393 @pathsuboption('pushrev', 'pushrev')
1408 @pathsuboption('pushrev', 'pushrev')
1394 def pushrevpathoption(ui, path, value):
1409 def pushrevpathoption(ui, path, value):
1395 return value
1410 return value
1396
1411
1397 class path(object):
1412 class path(object):
1398 """Represents an individual path and its configuration."""
1413 """Represents an individual path and its configuration."""
1399
1414
1400 def __init__(self, ui, name, rawloc=None, suboptions=None):
1415 def __init__(self, ui, name, rawloc=None, suboptions=None):
1401 """Construct a path from its config options.
1416 """Construct a path from its config options.
1402
1417
1403 ``ui`` is the ``ui`` instance the path is coming from.
1418 ``ui`` is the ``ui`` instance the path is coming from.
1404 ``name`` is the symbolic name of the path.
1419 ``name`` is the symbolic name of the path.
1405 ``rawloc`` is the raw location, as defined in the config.
1420 ``rawloc`` is the raw location, as defined in the config.
1406 ``pushloc`` is the raw locations pushes should be made to.
1421 ``pushloc`` is the raw locations pushes should be made to.
1407
1422
1408 If ``name`` is not defined, we require that the location be a) a local
1423 If ``name`` is not defined, we require that the location be a) a local
1409 filesystem path with a .hg directory or b) a URL. If not,
1424 filesystem path with a .hg directory or b) a URL. If not,
1410 ``ValueError`` is raised.
1425 ``ValueError`` is raised.
1411 """
1426 """
1412 if not rawloc:
1427 if not rawloc:
1413 raise ValueError('rawloc must be defined')
1428 raise ValueError('rawloc must be defined')
1414
1429
1415 # Locations may define branches via syntax <base>#<branch>.
1430 # Locations may define branches via syntax <base>#<branch>.
1416 u = util.url(rawloc)
1431 u = util.url(rawloc)
1417 branch = None
1432 branch = None
1418 if u.fragment:
1433 if u.fragment:
1419 branch = u.fragment
1434 branch = u.fragment
1420 u.fragment = None
1435 u.fragment = None
1421
1436
1422 self.url = u
1437 self.url = u
1423 self.branch = branch
1438 self.branch = branch
1424
1439
1425 self.name = name
1440 self.name = name
1426 self.rawloc = rawloc
1441 self.rawloc = rawloc
1427 self.loc = str(u)
1442 self.loc = str(u)
1428
1443
1429 # When given a raw location but not a symbolic name, validate the
1444 # When given a raw location but not a symbolic name, validate the
1430 # location is valid.
1445 # location is valid.
1431 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1446 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1432 raise ValueError('location is not a URL or path to a local '
1447 raise ValueError('location is not a URL or path to a local '
1433 'repo: %s' % rawloc)
1448 'repo: %s' % rawloc)
1434
1449
1435 suboptions = suboptions or {}
1450 suboptions = suboptions or {}
1436
1451
1437 # Now process the sub-options. If a sub-option is registered, its
1452 # Now process the sub-options. If a sub-option is registered, its
1438 # attribute will always be present. The value will be None if there
1453 # attribute will always be present. The value will be None if there
1439 # was no valid sub-option.
1454 # was no valid sub-option.
1440 for suboption, (attr, func) in _pathsuboptions.iteritems():
1455 for suboption, (attr, func) in _pathsuboptions.iteritems():
1441 if suboption not in suboptions:
1456 if suboption not in suboptions:
1442 setattr(self, attr, None)
1457 setattr(self, attr, None)
1443 continue
1458 continue
1444
1459
1445 value = func(ui, self, suboptions[suboption])
1460 value = func(ui, self, suboptions[suboption])
1446 setattr(self, attr, value)
1461 setattr(self, attr, value)
1447
1462
1448 def _isvalidlocalpath(self, path):
1463 def _isvalidlocalpath(self, path):
1449 """Returns True if the given path is a potentially valid repository.
1464 """Returns True if the given path is a potentially valid repository.
1450 This is its own function so that extensions can change the definition of
1465 This is its own function so that extensions can change the definition of
1451 'valid' in this case (like when pulling from a git repo into a hg
1466 'valid' in this case (like when pulling from a git repo into a hg
1452 one)."""
1467 one)."""
1453 return os.path.isdir(os.path.join(path, '.hg'))
1468 return os.path.isdir(os.path.join(path, '.hg'))
1454
1469
1455 @property
1470 @property
1456 def suboptions(self):
1471 def suboptions(self):
1457 """Return sub-options and their values for this path.
1472 """Return sub-options and their values for this path.
1458
1473
1459 This is intended to be used for presentation purposes.
1474 This is intended to be used for presentation purposes.
1460 """
1475 """
1461 d = {}
1476 d = {}
1462 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1477 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1463 value = getattr(self, attr)
1478 value = getattr(self, attr)
1464 if value is not None:
1479 if value is not None:
1465 d[subopt] = value
1480 d[subopt] = value
1466 return d
1481 return d
1467
1482
1468 # we instantiate one globally shared progress bar to avoid
1483 # we instantiate one globally shared progress bar to avoid
1469 # competing progress bars when multiple UI objects get created
1484 # competing progress bars when multiple UI objects get created
1470 _progresssingleton = None
1485 _progresssingleton = None
1471
1486
1472 def getprogbar(ui):
1487 def getprogbar(ui):
1473 global _progresssingleton
1488 global _progresssingleton
1474 if _progresssingleton is None:
1489 if _progresssingleton is None:
1475 # passing 'ui' object to the singleton is fishy,
1490 # passing 'ui' object to the singleton is fishy,
1476 # this is how the extension used to work but feel free to rework it.
1491 # this is how the extension used to work but feel free to rework it.
1477 _progresssingleton = progress.progbar(ui)
1492 _progresssingleton = progress.progbar(ui)
1478 return _progresssingleton
1493 return _progresssingleton
@@ -1,54 +1,70
1 Test if logtoprocess correctly captures command-related log calls.
1 Test if logtoprocess correctly captures command-related log calls.
2
2
3 $ hg init
3 $ hg init
4 $ cat > $TESTTMP/foocommand.py << EOF
4 $ cat > $TESTTMP/foocommand.py << EOF
5 > from mercurial import cmdutil
5 > from mercurial import cmdutil
6 > from time import sleep
6 > from time import sleep
7 > cmdtable = {}
7 > cmdtable = {}
8 > command = cmdutil.command(cmdtable)
8 > command = cmdutil.command(cmdtable)
9 > @command('foo', [])
9 > @command('foo', [])
10 > def foo(ui, repo):
10 > def foo(ui, repo):
11 > ui.log('foo', 'a message: %(bar)s\n', bar='spam')
11 > ui.log('foo', 'a message: %(bar)s\n', bar='spam')
12 > EOF
12 > EOF
13 $ cp $HGRCPATH $HGRCPATH.bak
13 $ cat >> $HGRCPATH << EOF
14 $ cat >> $HGRCPATH << EOF
14 > [extensions]
15 > [extensions]
15 > logtoprocess=
16 > logtoprocess=
16 > foocommand=$TESTTMP/foocommand.py
17 > foocommand=$TESTTMP/foocommand.py
17 > [logtoprocess]
18 > [logtoprocess]
18 > command=echo 'logtoprocess command output:';
19 > command=echo 'logtoprocess command output:';
19 > echo "\$EVENT";
20 > echo "\$EVENT";
20 > echo "\$MSG1";
21 > echo "\$MSG1";
21 > echo "\$MSG2"
22 > echo "\$MSG2"
22 > commandfinish=echo 'logtoprocess commandfinish output:';
23 > commandfinish=echo 'logtoprocess commandfinish output:';
23 > echo "\$EVENT";
24 > echo "\$EVENT";
24 > echo "\$MSG1";
25 > echo "\$MSG1";
25 > echo "\$MSG2";
26 > echo "\$MSG2";
26 > echo "\$MSG3"
27 > echo "\$MSG3"
27 > foo=echo 'logtoprocess foo output:';
28 > foo=echo 'logtoprocess foo output:';
28 > echo "\$EVENT";
29 > echo "\$EVENT";
29 > echo "\$MSG1";
30 > echo "\$MSG1";
30 > echo "\$OPT_BAR"
31 > echo "\$OPT_BAR"
31 > EOF
32 > EOF
32
33
33 Running a command triggers both a ui.log('command') and a
34 Running a command triggers both a ui.log('command') and a
34 ui.log('commandfinish') call. The foo command also uses ui.log.
35 ui.log('commandfinish') call. The foo command also uses ui.log.
35
36
36 Use head to ensure we wait for all lines to be produced, and sort to avoid
37 Use head to ensure we wait for all lines to be produced, and sort to avoid
37 ordering issues between the various processes we spawn:
38 ordering issues between the various processes we spawn:
38 $ hg foo | head -n 17 | sort
39 $ hg foo | head -n 17 | sort
39
40
40
41
41
42
42 0
43 0
43 a message: spam
44 a message: spam
44 command
45 command
45 commandfinish
46 commandfinish
46 foo
47 foo
47 foo
48 foo
48 foo
49 foo
49 foo
50 foo
50 foo exited 0 after * seconds (glob)
51 foo exited 0 after * seconds (glob)
51 logtoprocess command output:
52 logtoprocess command output:
52 logtoprocess commandfinish output:
53 logtoprocess commandfinish output:
53 logtoprocess foo output:
54 logtoprocess foo output:
54 spam
55 spam
56
57 Confirm that logging blocked time catches stdio properly:
58 $ cp $HGRCPATH.bak $HGRCPATH
59 $ cat >> $HGRCPATH << EOF
60 > [extensions]
61 > logtoprocess=
62 > pager=
63 > [logtoprocess]
64 > uiblocked=echo "\$EVENT command \$OPT_COMMAND_DURATION ms"
65 > [ui]
66 > logblockedtimes=True
67 > EOF
68
69 $ hg log
70 uiblocked command [0-9]+.[0-9]* ms (re)
General Comments 0
You need to be logged in to leave comments. Login now