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