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