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