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