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