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