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