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