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