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