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