##// END OF EJS Templates
ui: add prompt argument to write (issue5154) (API)...
timeless -
r28633:e35d7f13 default
parent child Browse files
Show More
@@ -1,655 +1,655 b''
1 1 # color.py color output for Mercurial commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.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 '''colorize output from some commands
9 9
10 10 The color extension colorizes output from several Mercurial commands.
11 11 For example, the diff command shows additions in green and deletions
12 12 in red, while the status command shows modified files in magenta. Many
13 13 other commands have analogous colors. It is possible to customize
14 14 these colors.
15 15
16 16 Effects
17 17 -------
18 18
19 19 Other effects in addition to color, like bold and underlined text, are
20 20 also available. By default, the terminfo database is used to find the
21 21 terminal codes used to change color and effect. If terminfo is not
22 22 available, then effects are rendered with the ECMA-48 SGR control
23 23 function (aka ANSI escape codes).
24 24
25 25 The available effects in terminfo mode are 'blink', 'bold', 'dim',
26 26 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
27 27 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
28 28 'underline'. How each is rendered depends on the terminal emulator.
29 29 Some may not be available for a given terminal type, and will be
30 30 silently ignored.
31 31
32 32 Labels
33 33 ------
34 34
35 35 Text receives color effects depending on the labels that it has. Many
36 36 default Mercurial commands emit labelled text. You can also define
37 37 your own labels in templates using the label function, see :hg:`help
38 38 templates`. A single portion of text may have more than one label. In
39 39 that case, effects given to the last label will override any other
40 40 effects. This includes the special "none" effect, which nullifies
41 41 other effects.
42 42
43 43 Labels are normally invisible. In order to see these labels and their
44 44 position in the text, use the global --color=debug option. The same
45 45 anchor text may be associated to multiple labels, e.g.
46 46
47 47 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
48 48
49 49 The following are the default effects for some default labels. Default
50 50 effects may be overridden from your configuration file::
51 51
52 52 [color]
53 53 status.modified = blue bold underline red_background
54 54 status.added = green bold
55 55 status.removed = red bold blue_background
56 56 status.deleted = cyan bold underline
57 57 status.unknown = magenta bold underline
58 58 status.ignored = black bold
59 59
60 60 # 'none' turns off all effects
61 61 status.clean = none
62 62 status.copied = none
63 63
64 64 qseries.applied = blue bold underline
65 65 qseries.unapplied = black bold
66 66 qseries.missing = red bold
67 67
68 68 diff.diffline = bold
69 69 diff.extended = cyan bold
70 70 diff.file_a = red bold
71 71 diff.file_b = green bold
72 72 diff.hunk = magenta
73 73 diff.deleted = red
74 74 diff.inserted = green
75 75 diff.changed = white
76 76 diff.tab =
77 77 diff.trailingwhitespace = bold red_background
78 78
79 79 # Blank so it inherits the style of the surrounding label
80 80 changeset.public =
81 81 changeset.draft =
82 82 changeset.secret =
83 83
84 84 resolve.unresolved = red bold
85 85 resolve.resolved = green bold
86 86
87 87 bookmarks.active = green
88 88
89 89 branches.active = none
90 90 branches.closed = black bold
91 91 branches.current = green
92 92 branches.inactive = none
93 93
94 94 tags.normal = green
95 95 tags.local = black bold
96 96
97 97 rebase.rebased = blue
98 98 rebase.remaining = red bold
99 99
100 100 shelve.age = cyan
101 101 shelve.newest = green bold
102 102 shelve.name = blue bold
103 103
104 104 histedit.remaining = red bold
105 105
106 106 Custom colors
107 107 -------------
108 108
109 109 Because there are only eight standard colors, this module allows you
110 110 to define color names for other color slots which might be available
111 111 for your terminal type, assuming terminfo mode. For instance::
112 112
113 113 color.brightblue = 12
114 114 color.pink = 207
115 115 color.orange = 202
116 116
117 117 to set 'brightblue' to color slot 12 (useful for 16 color terminals
118 118 that have brighter colors defined in the upper eight) and, 'pink' and
119 119 'orange' to colors in 256-color xterm's default color cube. These
120 120 defined colors may then be used as any of the pre-defined eight,
121 121 including appending '_background' to set the background to that color.
122 122
123 123 Modes
124 124 -----
125 125
126 126 By default, the color extension will use ANSI mode (or win32 mode on
127 127 Windows) if it detects a terminal. To override auto mode (to enable
128 128 terminfo mode, for example), set the following configuration option::
129 129
130 130 [color]
131 131 mode = terminfo
132 132
133 133 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
134 134 disable color.
135 135
136 136 Note that on some systems, terminfo mode may cause problems when using
137 137 color with the pager extension and less -R. less with the -R option
138 138 will only display ECMA-48 color codes, and terminfo mode may sometimes
139 139 emit codes that less doesn't understand. You can work around this by
140 140 either using ansi mode (or auto mode), or by using less -r (which will
141 141 pass through all terminal control codes, not just color control
142 142 codes).
143 143
144 144 On some systems (such as MSYS in Windows), the terminal may support
145 145 a different color mode than the pager (activated via the "pager"
146 146 extension). It is possible to define separate modes depending on whether
147 147 the pager is active::
148 148
149 149 [color]
150 150 mode = auto
151 151 pagermode = ansi
152 152
153 153 If ``pagermode`` is not defined, the ``mode`` will be used.
154 154 '''
155 155
156 156 import os
157 157
158 158 from mercurial import cmdutil, commands, dispatch, extensions, subrepo, util
159 159 from mercurial import ui as uimod
160 160 from mercurial.i18n import _
161 161
162 162 cmdtable = {}
163 163 command = cmdutil.command(cmdtable)
164 164 # Note for extension authors: ONLY specify testedwith = 'internal' for
165 165 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
166 166 # be specifying the version(s) of Mercurial they are tested with, or
167 167 # leave the attribute unspecified.
168 168 testedwith = 'internal'
169 169
170 170 # start and stop parameters for effects
171 171 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
172 172 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
173 173 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2,
174 174 'black_background': 40, 'red_background': 41,
175 175 'green_background': 42, 'yellow_background': 43,
176 176 'blue_background': 44, 'purple_background': 45,
177 177 'cyan_background': 46, 'white_background': 47}
178 178
179 179 def _terminfosetup(ui, mode):
180 180 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
181 181
182 182 global _terminfo_params
183 183 # If we failed to load curses, we go ahead and return.
184 184 if not _terminfo_params:
185 185 return
186 186 # Otherwise, see what the config file says.
187 187 if mode not in ('auto', 'terminfo'):
188 188 return
189 189
190 190 _terminfo_params.update((key[6:], (False, int(val)))
191 191 for key, val in ui.configitems('color')
192 192 if key.startswith('color.'))
193 193
194 194 try:
195 195 curses.setupterm()
196 196 except curses.error as e:
197 197 _terminfo_params = {}
198 198 return
199 199
200 200 for key, (b, e) in _terminfo_params.items():
201 201 if not b:
202 202 continue
203 203 if not curses.tigetstr(e):
204 204 # Most terminals don't support dim, invis, etc, so don't be
205 205 # noisy and use ui.debug().
206 206 ui.debug("no terminfo entry for %s\n" % e)
207 207 del _terminfo_params[key]
208 208 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
209 209 # Only warn about missing terminfo entries if we explicitly asked for
210 210 # terminfo mode.
211 211 if mode == "terminfo":
212 212 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
213 213 "ECMA-48 color\n"))
214 214 _terminfo_params = {}
215 215
216 216 def _modesetup(ui, coloropt):
217 217 global _terminfo_params
218 218
219 219 if coloropt == 'debug':
220 220 return 'debug'
221 221
222 222 auto = (coloropt == 'auto')
223 223 always = not auto and util.parsebool(coloropt)
224 224 if not always and not auto:
225 225 return None
226 226
227 227 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
228 228
229 229 mode = ui.config('color', 'mode', 'auto')
230 230
231 231 # If pager is active, color.pagermode overrides color.mode.
232 232 if getattr(ui, 'pageractive', False):
233 233 mode = ui.config('color', 'pagermode', mode)
234 234
235 235 realmode = mode
236 236 if mode == 'auto':
237 237 if os.name == 'nt':
238 238 term = os.environ.get('TERM')
239 239 # TERM won't be defined in a vanilla cmd.exe environment.
240 240
241 241 # UNIX-like environments on Windows such as Cygwin and MSYS will
242 242 # set TERM. They appear to make a best effort attempt at setting it
243 243 # to something appropriate. However, not all environments with TERM
244 244 # defined support ANSI. Since "ansi" could result in terminal
245 245 # gibberish, we error on the side of selecting "win32". However, if
246 246 # w32effects is not defined, we almost certainly don't support
247 247 # "win32", so don't even try.
248 248 if (term and 'xterm' in term) or not w32effects:
249 249 realmode = 'ansi'
250 250 else:
251 251 realmode = 'win32'
252 252 else:
253 253 realmode = 'ansi'
254 254
255 255 def modewarn():
256 256 # only warn if color.mode was explicitly set and we're in
257 257 # an interactive terminal
258 258 if mode == realmode and ui.interactive():
259 259 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
260 260
261 261 if realmode == 'win32':
262 262 _terminfo_params = {}
263 263 if not w32effects:
264 264 modewarn()
265 265 return None
266 266 _effects.update(w32effects)
267 267 elif realmode == 'ansi':
268 268 _terminfo_params = {}
269 269 elif realmode == 'terminfo':
270 270 _terminfosetup(ui, mode)
271 271 if not _terminfo_params:
272 272 ## FIXME Shouldn't we return None in this case too?
273 273 modewarn()
274 274 realmode = 'ansi'
275 275 else:
276 276 return None
277 277
278 278 if always or (auto and formatted):
279 279 return realmode
280 280 return None
281 281
282 282 try:
283 283 import curses
284 284 # Mapping from effect name to terminfo attribute name or color number.
285 285 # This will also force-load the curses module.
286 286 _terminfo_params = {'none': (True, 'sgr0'),
287 287 'standout': (True, 'smso'),
288 288 'underline': (True, 'smul'),
289 289 'reverse': (True, 'rev'),
290 290 'inverse': (True, 'rev'),
291 291 'blink': (True, 'blink'),
292 292 'dim': (True, 'dim'),
293 293 'bold': (True, 'bold'),
294 294 'invisible': (True, 'invis'),
295 295 'italic': (True, 'sitm'),
296 296 'black': (False, curses.COLOR_BLACK),
297 297 'red': (False, curses.COLOR_RED),
298 298 'green': (False, curses.COLOR_GREEN),
299 299 'yellow': (False, curses.COLOR_YELLOW),
300 300 'blue': (False, curses.COLOR_BLUE),
301 301 'magenta': (False, curses.COLOR_MAGENTA),
302 302 'cyan': (False, curses.COLOR_CYAN),
303 303 'white': (False, curses.COLOR_WHITE)}
304 304 except ImportError:
305 305 _terminfo_params = {}
306 306
307 307 _styles = {'grep.match': 'red bold',
308 308 'grep.linenumber': 'green',
309 309 'grep.rev': 'green',
310 310 'grep.change': 'green',
311 311 'grep.sep': 'cyan',
312 312 'grep.filename': 'magenta',
313 313 'grep.user': 'magenta',
314 314 'grep.date': 'magenta',
315 315 'bookmarks.active': 'green',
316 316 'branches.active': 'none',
317 317 'branches.closed': 'black bold',
318 318 'branches.current': 'green',
319 319 'branches.inactive': 'none',
320 320 'diff.changed': 'white',
321 321 'diff.deleted': 'red',
322 322 'diff.diffline': 'bold',
323 323 'diff.extended': 'cyan bold',
324 324 'diff.file_a': 'red bold',
325 325 'diff.file_b': 'green bold',
326 326 'diff.hunk': 'magenta',
327 327 'diff.inserted': 'green',
328 328 'diff.tab': '',
329 329 'diff.trailingwhitespace': 'bold red_background',
330 330 'changeset.public' : '',
331 331 'changeset.draft' : '',
332 332 'changeset.secret' : '',
333 333 'diffstat.deleted': 'red',
334 334 'diffstat.inserted': 'green',
335 335 'histedit.remaining': 'red bold',
336 336 'ui.prompt': 'yellow',
337 337 'log.changeset': 'yellow',
338 338 'patchbomb.finalsummary': '',
339 339 'patchbomb.from': 'magenta',
340 340 'patchbomb.to': 'cyan',
341 341 'patchbomb.subject': 'green',
342 342 'patchbomb.diffstats': '',
343 343 'rebase.rebased': 'blue',
344 344 'rebase.remaining': 'red bold',
345 345 'resolve.resolved': 'green bold',
346 346 'resolve.unresolved': 'red bold',
347 347 'shelve.age': 'cyan',
348 348 'shelve.newest': 'green bold',
349 349 'shelve.name': 'blue bold',
350 350 'status.added': 'green bold',
351 351 'status.clean': 'none',
352 352 'status.copied': 'none',
353 353 'status.deleted': 'cyan bold underline',
354 354 'status.ignored': 'black bold',
355 355 'status.modified': 'blue bold',
356 356 'status.removed': 'red bold',
357 357 'status.unknown': 'magenta bold underline',
358 358 'tags.normal': 'green',
359 359 'tags.local': 'black bold'}
360 360
361 361
362 362 def _effect_str(effect):
363 363 '''Helper function for render_effects().'''
364 364
365 365 bg = False
366 366 if effect.endswith('_background'):
367 367 bg = True
368 368 effect = effect[:-11]
369 369 attr, val = _terminfo_params[effect]
370 370 if attr:
371 371 return curses.tigetstr(val)
372 372 elif bg:
373 373 return curses.tparm(curses.tigetstr('setab'), val)
374 374 else:
375 375 return curses.tparm(curses.tigetstr('setaf'), val)
376 376
377 377 def render_effects(text, effects):
378 378 'Wrap text in commands to turn on each effect.'
379 379 if not text:
380 380 return text
381 381 if not _terminfo_params:
382 382 start = [str(_effects[e]) for e in ['none'] + effects.split()]
383 383 start = '\033[' + ';'.join(start) + 'm'
384 384 stop = '\033[' + str(_effects['none']) + 'm'
385 385 else:
386 386 start = ''.join(_effect_str(effect)
387 387 for effect in ['none'] + effects.split())
388 388 stop = _effect_str('none')
389 389 return ''.join([start, text, stop])
390 390
391 391 def extstyles():
392 392 for name, ext in extensions.extensions():
393 393 _styles.update(getattr(ext, 'colortable', {}))
394 394
395 395 def valideffect(effect):
396 396 'Determine if the effect is valid or not.'
397 397 good = False
398 398 if not _terminfo_params and effect in _effects:
399 399 good = True
400 400 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
401 401 good = True
402 402 return good
403 403
404 404 def configstyles(ui):
405 405 for status, cfgeffects in ui.configitems('color'):
406 406 if '.' not in status or status.startswith('color.'):
407 407 continue
408 408 cfgeffects = ui.configlist('color', status)
409 409 if cfgeffects:
410 410 good = []
411 411 for e in cfgeffects:
412 412 if valideffect(e):
413 413 good.append(e)
414 414 else:
415 415 ui.warn(_("ignoring unknown color/effect %r "
416 416 "(configured in color.%s)\n")
417 417 % (e, status))
418 418 _styles[status] = ' '.join(good)
419 419
420 420 class colorui(uimod.ui):
421 421 _colormode = 'ansi'
422 422 def write(self, *args, **opts):
423 423 if self._colormode is None:
424 424 return super(colorui, self).write(*args, **opts)
425 425
426 426 label = opts.get('label', '')
427 if self._buffers:
427 if self._buffers and not opts.get('prompt', False):
428 428 if self._bufferapplylabels:
429 429 self._buffers[-1].extend(self.label(a, label) for a in args)
430 430 else:
431 431 self._buffers[-1].extend(args)
432 432 elif self._colormode == 'win32':
433 433 for a in args:
434 434 win32print(a, super(colorui, self).write, **opts)
435 435 else:
436 436 return super(colorui, self).write(
437 437 *[self.label(a, label) for a in args], **opts)
438 438
439 439 def write_err(self, *args, **opts):
440 440 if self._colormode is None:
441 441 return super(colorui, self).write_err(*args, **opts)
442 442
443 443 label = opts.get('label', '')
444 444 if self._bufferstates and self._bufferstates[-1][0]:
445 445 return self.write(*args, **opts)
446 446 if self._colormode == 'win32':
447 447 for a in args:
448 448 win32print(a, super(colorui, self).write_err, **opts)
449 449 else:
450 450 return super(colorui, self).write_err(
451 451 *[self.label(a, label) for a in args], **opts)
452 452
453 453 def showlabel(self, msg, label):
454 454 if label and msg:
455 455 if msg[-1] == '\n':
456 456 return "[%s|%s]\n" % (label, msg[:-1])
457 457 else:
458 458 return "[%s|%s]" % (label, msg)
459 459 else:
460 460 return msg
461 461
462 462 def label(self, msg, label):
463 463 if self._colormode is None:
464 464 return super(colorui, self).label(msg, label)
465 465
466 466 if self._colormode == 'debug':
467 467 return self.showlabel(msg, label)
468 468
469 469 effects = []
470 470 for l in label.split():
471 471 s = _styles.get(l, '')
472 472 if s:
473 473 effects.append(s)
474 474 elif valideffect(l):
475 475 effects.append(l)
476 476 effects = ' '.join(effects)
477 477 if effects:
478 478 return '\n'.join([render_effects(s, effects)
479 479 for s in msg.split('\n')])
480 480 return msg
481 481
482 482 def uisetup(ui):
483 483 if ui.plain():
484 484 return
485 485 if not isinstance(ui, colorui):
486 486 colorui.__bases__ = (ui.__class__,)
487 487 ui.__class__ = colorui
488 488 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
489 489 mode = _modesetup(ui_, opts['color'])
490 490 colorui._colormode = mode
491 491 if mode and mode != 'debug':
492 492 extstyles()
493 493 configstyles(ui_)
494 494 return orig(ui_, opts, cmd, cmdfunc)
495 495 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
496 496 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
497 497 # insert the argument in the front,
498 498 # the end of git diff arguments is used for paths
499 499 commands.insert(1, '--color')
500 500 return orig(gitsub, commands, env, stream, cwd)
501 501 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
502 502 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
503 503
504 504 def extsetup(ui):
505 505 commands.globalopts.append(
506 506 ('', 'color', 'auto',
507 507 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
508 508 # and should not be translated
509 509 _("when to colorize (boolean, always, auto, never, or debug)"),
510 510 _('TYPE')))
511 511
512 512 @command('debugcolor', [], 'hg debugcolor')
513 513 def debugcolor(ui, repo, **opts):
514 514 global _styles
515 515 _styles = {}
516 516 for effect in _effects.keys():
517 517 _styles[effect] = effect
518 518 ui.write(('color mode: %s\n') % ui._colormode)
519 519 ui.write(_('available colors:\n'))
520 520 for label, colors in _styles.items():
521 521 ui.write(('%s\n') % colors, label=label)
522 522
523 523 if os.name != 'nt':
524 524 w32effects = None
525 525 else:
526 526 import re, ctypes
527 527
528 528 _kernel32 = ctypes.windll.kernel32
529 529
530 530 _WORD = ctypes.c_ushort
531 531
532 532 _INVALID_HANDLE_VALUE = -1
533 533
534 534 class _COORD(ctypes.Structure):
535 535 _fields_ = [('X', ctypes.c_short),
536 536 ('Y', ctypes.c_short)]
537 537
538 538 class _SMALL_RECT(ctypes.Structure):
539 539 _fields_ = [('Left', ctypes.c_short),
540 540 ('Top', ctypes.c_short),
541 541 ('Right', ctypes.c_short),
542 542 ('Bottom', ctypes.c_short)]
543 543
544 544 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
545 545 _fields_ = [('dwSize', _COORD),
546 546 ('dwCursorPosition', _COORD),
547 547 ('wAttributes', _WORD),
548 548 ('srWindow', _SMALL_RECT),
549 549 ('dwMaximumWindowSize', _COORD)]
550 550
551 551 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
552 552 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
553 553
554 554 _FOREGROUND_BLUE = 0x0001
555 555 _FOREGROUND_GREEN = 0x0002
556 556 _FOREGROUND_RED = 0x0004
557 557 _FOREGROUND_INTENSITY = 0x0008
558 558
559 559 _BACKGROUND_BLUE = 0x0010
560 560 _BACKGROUND_GREEN = 0x0020
561 561 _BACKGROUND_RED = 0x0040
562 562 _BACKGROUND_INTENSITY = 0x0080
563 563
564 564 _COMMON_LVB_REVERSE_VIDEO = 0x4000
565 565 _COMMON_LVB_UNDERSCORE = 0x8000
566 566
567 567 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
568 568 w32effects = {
569 569 'none': -1,
570 570 'black': 0,
571 571 'red': _FOREGROUND_RED,
572 572 'green': _FOREGROUND_GREEN,
573 573 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
574 574 'blue': _FOREGROUND_BLUE,
575 575 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
576 576 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
577 577 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
578 578 'bold': _FOREGROUND_INTENSITY,
579 579 'black_background': 0x100, # unused value > 0x0f
580 580 'red_background': _BACKGROUND_RED,
581 581 'green_background': _BACKGROUND_GREEN,
582 582 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
583 583 'blue_background': _BACKGROUND_BLUE,
584 584 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
585 585 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
586 586 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
587 587 _BACKGROUND_BLUE),
588 588 'bold_background': _BACKGROUND_INTENSITY,
589 589 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
590 590 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
591 591 }
592 592
593 593 passthrough = set([_FOREGROUND_INTENSITY,
594 594 _BACKGROUND_INTENSITY,
595 595 _COMMON_LVB_UNDERSCORE,
596 596 _COMMON_LVB_REVERSE_VIDEO])
597 597
598 598 stdout = _kernel32.GetStdHandle(
599 599 _STD_OUTPUT_HANDLE) # don't close the handle returned
600 600 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
601 601 w32effects = None
602 602 else:
603 603 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
604 604 if not _kernel32.GetConsoleScreenBufferInfo(
605 605 stdout, ctypes.byref(csbi)):
606 606 # stdout may not support GetConsoleScreenBufferInfo()
607 607 # when called from subprocess or redirected
608 608 w32effects = None
609 609 else:
610 610 origattr = csbi.wAttributes
611 611 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
612 612 re.MULTILINE | re.DOTALL)
613 613
614 614 def win32print(text, orig, **opts):
615 615 label = opts.get('label', '')
616 616 attr = origattr
617 617
618 618 def mapcolor(val, attr):
619 619 if val == -1:
620 620 return origattr
621 621 elif val in passthrough:
622 622 return attr | val
623 623 elif val > 0x0f:
624 624 return (val & 0x70) | (attr & 0x8f)
625 625 else:
626 626 return (val & 0x07) | (attr & 0xf8)
627 627
628 628 # determine console attributes based on labels
629 629 for l in label.split():
630 630 style = _styles.get(l, '')
631 631 for effect in style.split():
632 632 try:
633 633 attr = mapcolor(w32effects[effect], attr)
634 634 except KeyError:
635 635 # w32effects could not have certain attributes so we skip
636 636 # them if not found
637 637 pass
638 638 # hack to ensure regexp finds data
639 639 if not text.startswith('\033['):
640 640 text = '\033[m' + text
641 641
642 642 # Look for ANSI-like codes embedded in text
643 643 m = re.match(ansire, text)
644 644
645 645 try:
646 646 while m:
647 647 for sattr in m.group(1).split(';'):
648 648 if sattr:
649 649 attr = mapcolor(int(sattr), attr)
650 650 _kernel32.SetConsoleTextAttribute(stdout, attr)
651 651 orig(m.group(2), **opts)
652 652 m = re.match(ansire, m.group(3))
653 653 finally:
654 654 # Explicitly reset original attributes
655 655 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,1335 +1,1335 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 errno
11 11 import getpass
12 12 import inspect
13 13 import os
14 14 import re
15 15 import socket
16 16 import sys
17 17 import tempfile
18 18 import traceback
19 19
20 20 from .i18n import _
21 21 from .node import hex
22 22
23 23 from . import (
24 24 config,
25 25 error,
26 26 formatter,
27 27 progress,
28 28 scmutil,
29 29 util,
30 30 )
31 31
32 32 samplehgrcs = {
33 33 'user':
34 34 """# example user config (see "hg help config" for more info)
35 35 [ui]
36 36 # name and email, e.g.
37 37 # username = Jane Doe <jdoe@example.com>
38 38 username =
39 39
40 40 [extensions]
41 41 # uncomment these lines to enable some popular extensions
42 42 # (see "hg help extensions" for more info)
43 43 #
44 44 # pager =
45 45 # color =""",
46 46
47 47 'cloned':
48 48 """# example repository config (see "hg help config" for more info)
49 49 [paths]
50 50 default = %s
51 51
52 52 # path aliases to other clones of this repo in URLs or filesystem paths
53 53 # (see "hg help config.paths" for more info)
54 54 #
55 55 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
56 56 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
57 57 # my-clone = /home/jdoe/jdoes-clone
58 58
59 59 [ui]
60 60 # name and email (local to this repository, optional), e.g.
61 61 # username = Jane Doe <jdoe@example.com>
62 62 """,
63 63
64 64 'local':
65 65 """# example repository config (see "hg help config" for more info)
66 66 [paths]
67 67 # path aliases to other clones of this repo in URLs or filesystem paths
68 68 # (see "hg help config.paths" for more info)
69 69 #
70 70 # default = http://example.com/hg/example-repo
71 71 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
72 72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 73 # my-clone = /home/jdoe/jdoes-clone
74 74
75 75 [ui]
76 76 # name and email (local to this repository, optional), e.g.
77 77 # username = Jane Doe <jdoe@example.com>
78 78 """,
79 79
80 80 'global':
81 81 """# example system-wide hg config (see "hg help config" for more info)
82 82
83 83 [extensions]
84 84 # uncomment these lines to enable some popular extensions
85 85 # (see "hg help extensions" for more info)
86 86 #
87 87 # blackbox =
88 88 # color =
89 89 # pager =""",
90 90 }
91 91
92 92 class ui(object):
93 93 def __init__(self, src=None):
94 94 # _buffers: used for temporary capture of output
95 95 self._buffers = []
96 96 # 3-tuple describing how each buffer in the stack behaves.
97 97 # Values are (capture stderr, capture subprocesses, apply labels).
98 98 self._bufferstates = []
99 99 # When a buffer is active, defines whether we are expanding labels.
100 100 # This exists to prevent an extra list lookup.
101 101 self._bufferapplylabels = None
102 102 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
103 103 self._reportuntrusted = True
104 104 self._ocfg = config.config() # overlay
105 105 self._tcfg = config.config() # trusted
106 106 self._ucfg = config.config() # untrusted
107 107 self._trustusers = set()
108 108 self._trustgroups = set()
109 109 self.callhooks = True
110 110
111 111 if src:
112 112 self.fout = src.fout
113 113 self.ferr = src.ferr
114 114 self.fin = src.fin
115 115
116 116 self._tcfg = src._tcfg.copy()
117 117 self._ucfg = src._ucfg.copy()
118 118 self._ocfg = src._ocfg.copy()
119 119 self._trustusers = src._trustusers.copy()
120 120 self._trustgroups = src._trustgroups.copy()
121 121 self.environ = src.environ
122 122 self.callhooks = src.callhooks
123 123 self.fixconfig()
124 124 else:
125 125 self.fout = sys.stdout
126 126 self.ferr = sys.stderr
127 127 self.fin = sys.stdin
128 128
129 129 # shared read-only environment
130 130 self.environ = os.environ
131 131 # we always trust global config files
132 132 for f in scmutil.rcpath():
133 133 self.readconfig(f, trust=True)
134 134
135 135 def copy(self):
136 136 return self.__class__(self)
137 137
138 138 def formatter(self, topic, opts):
139 139 return formatter.formatter(self, topic, opts)
140 140
141 141 def _trusted(self, fp, f):
142 142 st = util.fstat(fp)
143 143 if util.isowner(st):
144 144 return True
145 145
146 146 tusers, tgroups = self._trustusers, self._trustgroups
147 147 if '*' in tusers or '*' in tgroups:
148 148 return True
149 149
150 150 user = util.username(st.st_uid)
151 151 group = util.groupname(st.st_gid)
152 152 if user in tusers or group in tgroups or user == util.username():
153 153 return True
154 154
155 155 if self._reportuntrusted:
156 156 self.warn(_('not trusting file %s from untrusted '
157 157 'user %s, group %s\n') % (f, user, group))
158 158 return False
159 159
160 160 def readconfig(self, filename, root=None, trust=False,
161 161 sections=None, remap=None):
162 162 try:
163 163 fp = open(filename)
164 164 except IOError:
165 165 if not sections: # ignore unless we were looking for something
166 166 return
167 167 raise
168 168
169 169 cfg = config.config()
170 170 trusted = sections or trust or self._trusted(fp, filename)
171 171
172 172 try:
173 173 cfg.read(filename, fp, sections=sections, remap=remap)
174 174 fp.close()
175 175 except error.ConfigError as inst:
176 176 if trusted:
177 177 raise
178 178 self.warn(_("ignored: %s\n") % str(inst))
179 179
180 180 if self.plain():
181 181 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
182 182 'logtemplate', 'statuscopies', 'style',
183 183 'traceback', 'verbose'):
184 184 if k in cfg['ui']:
185 185 del cfg['ui'][k]
186 186 for k, v in cfg.items('defaults'):
187 187 del cfg['defaults'][k]
188 188 # Don't remove aliases from the configuration if in the exceptionlist
189 189 if self.plain('alias'):
190 190 for k, v in cfg.items('alias'):
191 191 del cfg['alias'][k]
192 192 if self.plain('revsetalias'):
193 193 for k, v in cfg.items('revsetalias'):
194 194 del cfg['revsetalias'][k]
195 195
196 196 if trusted:
197 197 self._tcfg.update(cfg)
198 198 self._tcfg.update(self._ocfg)
199 199 self._ucfg.update(cfg)
200 200 self._ucfg.update(self._ocfg)
201 201
202 202 if root is None:
203 203 root = os.path.expanduser('~')
204 204 self.fixconfig(root=root)
205 205
206 206 def fixconfig(self, root=None, section=None):
207 207 if section in (None, 'paths'):
208 208 # expand vars and ~
209 209 # translate paths relative to root (or home) into absolute paths
210 210 root = root or os.getcwd()
211 211 for c in self._tcfg, self._ucfg, self._ocfg:
212 212 for n, p in c.items('paths'):
213 213 if not p:
214 214 continue
215 215 if '%%' in p:
216 216 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
217 217 % (n, p, self.configsource('paths', n)))
218 218 p = p.replace('%%', '%')
219 219 p = util.expandpath(p)
220 220 if not util.hasscheme(p) and not os.path.isabs(p):
221 221 p = os.path.normpath(os.path.join(root, p))
222 222 c.set("paths", n, p)
223 223
224 224 if section in (None, 'ui'):
225 225 # update ui options
226 226 self.debugflag = self.configbool('ui', 'debug')
227 227 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
228 228 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
229 229 if self.verbose and self.quiet:
230 230 self.quiet = self.verbose = False
231 231 self._reportuntrusted = self.debugflag or self.configbool("ui",
232 232 "report_untrusted", True)
233 233 self.tracebackflag = self.configbool('ui', 'traceback', False)
234 234
235 235 if section in (None, 'trusted'):
236 236 # update trust information
237 237 self._trustusers.update(self.configlist('trusted', 'users'))
238 238 self._trustgroups.update(self.configlist('trusted', 'groups'))
239 239
240 240 def backupconfig(self, section, item):
241 241 return (self._ocfg.backup(section, item),
242 242 self._tcfg.backup(section, item),
243 243 self._ucfg.backup(section, item),)
244 244 def restoreconfig(self, data):
245 245 self._ocfg.restore(data[0])
246 246 self._tcfg.restore(data[1])
247 247 self._ucfg.restore(data[2])
248 248
249 249 def setconfig(self, section, name, value, source=''):
250 250 for cfg in (self._ocfg, self._tcfg, self._ucfg):
251 251 cfg.set(section, name, value, source)
252 252 self.fixconfig(section=section)
253 253
254 254 def _data(self, untrusted):
255 255 return untrusted and self._ucfg or self._tcfg
256 256
257 257 def configsource(self, section, name, untrusted=False):
258 258 return self._data(untrusted).source(section, name) or 'none'
259 259
260 260 def config(self, section, name, default=None, untrusted=False):
261 261 if isinstance(name, list):
262 262 alternates = name
263 263 else:
264 264 alternates = [name]
265 265
266 266 for n in alternates:
267 267 value = self._data(untrusted).get(section, n, None)
268 268 if value is not None:
269 269 name = n
270 270 break
271 271 else:
272 272 value = default
273 273
274 274 if self.debugflag and not untrusted and self._reportuntrusted:
275 275 for n in alternates:
276 276 uvalue = self._ucfg.get(section, n)
277 277 if uvalue is not None and uvalue != value:
278 278 self.debug("ignoring untrusted configuration option "
279 279 "%s.%s = %s\n" % (section, n, uvalue))
280 280 return value
281 281
282 282 def configsuboptions(self, section, name, default=None, untrusted=False):
283 283 """Get a config option and all sub-options.
284 284
285 285 Some config options have sub-options that are declared with the
286 286 format "key:opt = value". This method is used to return the main
287 287 option and all its declared sub-options.
288 288
289 289 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
290 290 is a dict of defined sub-options where keys and values are strings.
291 291 """
292 292 data = self._data(untrusted)
293 293 main = data.get(section, name, default)
294 294 if self.debugflag and not untrusted and self._reportuntrusted:
295 295 uvalue = self._ucfg.get(section, name)
296 296 if uvalue is not None and uvalue != main:
297 297 self.debug('ignoring untrusted configuration option '
298 298 '%s.%s = %s\n' % (section, name, uvalue))
299 299
300 300 sub = {}
301 301 prefix = '%s:' % name
302 302 for k, v in data.items(section):
303 303 if k.startswith(prefix):
304 304 sub[k[len(prefix):]] = v
305 305
306 306 if self.debugflag and not untrusted and self._reportuntrusted:
307 307 for k, v in sub.items():
308 308 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
309 309 if uvalue is not None and uvalue != v:
310 310 self.debug('ignoring untrusted configuration option '
311 311 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
312 312
313 313 return main, sub
314 314
315 315 def configpath(self, section, name, default=None, untrusted=False):
316 316 'get a path config item, expanded relative to repo root or config file'
317 317 v = self.config(section, name, default, untrusted)
318 318 if v is None:
319 319 return None
320 320 if not os.path.isabs(v) or "://" not in v:
321 321 src = self.configsource(section, name, untrusted)
322 322 if ':' in src:
323 323 base = os.path.dirname(src.rsplit(':')[0])
324 324 v = os.path.join(base, os.path.expanduser(v))
325 325 return v
326 326
327 327 def configbool(self, section, name, default=False, untrusted=False):
328 328 """parse a configuration element as a boolean
329 329
330 330 >>> u = ui(); s = 'foo'
331 331 >>> u.setconfig(s, 'true', 'yes')
332 332 >>> u.configbool(s, 'true')
333 333 True
334 334 >>> u.setconfig(s, 'false', 'no')
335 335 >>> u.configbool(s, 'false')
336 336 False
337 337 >>> u.configbool(s, 'unknown')
338 338 False
339 339 >>> u.configbool(s, 'unknown', True)
340 340 True
341 341 >>> u.setconfig(s, 'invalid', 'somevalue')
342 342 >>> u.configbool(s, 'invalid')
343 343 Traceback (most recent call last):
344 344 ...
345 345 ConfigError: foo.invalid is not a boolean ('somevalue')
346 346 """
347 347
348 348 v = self.config(section, name, None, untrusted)
349 349 if v is None:
350 350 return default
351 351 if isinstance(v, bool):
352 352 return v
353 353 b = util.parsebool(v)
354 354 if b is None:
355 355 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
356 356 % (section, name, v))
357 357 return b
358 358
359 359 def configint(self, section, name, default=None, untrusted=False):
360 360 """parse a configuration element as an integer
361 361
362 362 >>> u = ui(); s = 'foo'
363 363 >>> u.setconfig(s, 'int1', '42')
364 364 >>> u.configint(s, 'int1')
365 365 42
366 366 >>> u.setconfig(s, 'int2', '-42')
367 367 >>> u.configint(s, 'int2')
368 368 -42
369 369 >>> u.configint(s, 'unknown', 7)
370 370 7
371 371 >>> u.setconfig(s, 'invalid', 'somevalue')
372 372 >>> u.configint(s, 'invalid')
373 373 Traceback (most recent call last):
374 374 ...
375 375 ConfigError: foo.invalid is not an integer ('somevalue')
376 376 """
377 377
378 378 v = self.config(section, name, None, untrusted)
379 379 if v is None:
380 380 return default
381 381 try:
382 382 return int(v)
383 383 except ValueError:
384 384 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
385 385 % (section, name, v))
386 386
387 387 def configbytes(self, section, name, default=0, untrusted=False):
388 388 """parse a configuration element as a quantity in bytes
389 389
390 390 Units can be specified as b (bytes), k or kb (kilobytes), m or
391 391 mb (megabytes), g or gb (gigabytes).
392 392
393 393 >>> u = ui(); s = 'foo'
394 394 >>> u.setconfig(s, 'val1', '42')
395 395 >>> u.configbytes(s, 'val1')
396 396 42
397 397 >>> u.setconfig(s, 'val2', '42.5 kb')
398 398 >>> u.configbytes(s, 'val2')
399 399 43520
400 400 >>> u.configbytes(s, 'unknown', '7 MB')
401 401 7340032
402 402 >>> u.setconfig(s, 'invalid', 'somevalue')
403 403 >>> u.configbytes(s, 'invalid')
404 404 Traceback (most recent call last):
405 405 ...
406 406 ConfigError: foo.invalid is not a byte quantity ('somevalue')
407 407 """
408 408
409 409 value = self.config(section, name)
410 410 if value is None:
411 411 if not isinstance(default, str):
412 412 return default
413 413 value = default
414 414 try:
415 415 return util.sizetoint(value)
416 416 except error.ParseError:
417 417 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
418 418 % (section, name, value))
419 419
420 420 def configlist(self, section, name, default=None, untrusted=False):
421 421 """parse a configuration element as a list of comma/space separated
422 422 strings
423 423
424 424 >>> u = ui(); s = 'foo'
425 425 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
426 426 >>> u.configlist(s, 'list1')
427 427 ['this', 'is', 'a small', 'test']
428 428 """
429 429
430 430 def _parse_plain(parts, s, offset):
431 431 whitespace = False
432 432 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
433 433 whitespace = True
434 434 offset += 1
435 435 if offset >= len(s):
436 436 return None, parts, offset
437 437 if whitespace:
438 438 parts.append('')
439 439 if s[offset] == '"' and not parts[-1]:
440 440 return _parse_quote, parts, offset + 1
441 441 elif s[offset] == '"' and parts[-1][-1] == '\\':
442 442 parts[-1] = parts[-1][:-1] + s[offset]
443 443 return _parse_plain, parts, offset + 1
444 444 parts[-1] += s[offset]
445 445 return _parse_plain, parts, offset + 1
446 446
447 447 def _parse_quote(parts, s, offset):
448 448 if offset < len(s) and s[offset] == '"': # ""
449 449 parts.append('')
450 450 offset += 1
451 451 while offset < len(s) and (s[offset].isspace() or
452 452 s[offset] == ','):
453 453 offset += 1
454 454 return _parse_plain, parts, offset
455 455
456 456 while offset < len(s) and s[offset] != '"':
457 457 if (s[offset] == '\\' and offset + 1 < len(s)
458 458 and s[offset + 1] == '"'):
459 459 offset += 1
460 460 parts[-1] += '"'
461 461 else:
462 462 parts[-1] += s[offset]
463 463 offset += 1
464 464
465 465 if offset >= len(s):
466 466 real_parts = _configlist(parts[-1])
467 467 if not real_parts:
468 468 parts[-1] = '"'
469 469 else:
470 470 real_parts[0] = '"' + real_parts[0]
471 471 parts = parts[:-1]
472 472 parts.extend(real_parts)
473 473 return None, parts, offset
474 474
475 475 offset += 1
476 476 while offset < len(s) and s[offset] in [' ', ',']:
477 477 offset += 1
478 478
479 479 if offset < len(s):
480 480 if offset + 1 == len(s) and s[offset] == '"':
481 481 parts[-1] += '"'
482 482 offset += 1
483 483 else:
484 484 parts.append('')
485 485 else:
486 486 return None, parts, offset
487 487
488 488 return _parse_plain, parts, offset
489 489
490 490 def _configlist(s):
491 491 s = s.rstrip(' ,')
492 492 if not s:
493 493 return []
494 494 parser, parts, offset = _parse_plain, [''], 0
495 495 while parser:
496 496 parser, parts, offset = parser(parts, s, offset)
497 497 return parts
498 498
499 499 result = self.config(section, name, untrusted=untrusted)
500 500 if result is None:
501 501 result = default or []
502 502 if isinstance(result, basestring):
503 503 result = _configlist(result.lstrip(' ,\n'))
504 504 if result is None:
505 505 result = default or []
506 506 return result
507 507
508 508 def hasconfig(self, section, name, untrusted=False):
509 509 return self._data(untrusted).hasitem(section, name)
510 510
511 511 def has_section(self, section, untrusted=False):
512 512 '''tell whether section exists in config.'''
513 513 return section in self._data(untrusted)
514 514
515 515 def configitems(self, section, untrusted=False, ignoresub=False):
516 516 items = self._data(untrusted).items(section)
517 517 if ignoresub:
518 518 newitems = {}
519 519 for k, v in items:
520 520 if ':' not in k:
521 521 newitems[k] = v
522 522 items = newitems.items()
523 523 if self.debugflag and not untrusted and self._reportuntrusted:
524 524 for k, v in self._ucfg.items(section):
525 525 if self._tcfg.get(section, k) != v:
526 526 self.debug("ignoring untrusted configuration option "
527 527 "%s.%s = %s\n" % (section, k, v))
528 528 return items
529 529
530 530 def walkconfig(self, untrusted=False):
531 531 cfg = self._data(untrusted)
532 532 for section in cfg.sections():
533 533 for name, value in self.configitems(section, untrusted):
534 534 yield section, name, value
535 535
536 536 def plain(self, feature=None):
537 537 '''is plain mode active?
538 538
539 539 Plain mode means that all configuration variables which affect
540 540 the behavior and output of Mercurial should be
541 541 ignored. Additionally, the output should be stable,
542 542 reproducible and suitable for use in scripts or applications.
543 543
544 544 The only way to trigger plain mode is by setting either the
545 545 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
546 546
547 547 The return value can either be
548 548 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
549 549 - True otherwise
550 550 '''
551 551 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
552 552 return False
553 553 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
554 554 if feature and exceptions:
555 555 return feature not in exceptions
556 556 return True
557 557
558 558 def username(self):
559 559 """Return default username to be used in commits.
560 560
561 561 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
562 562 and stop searching if one of these is set.
563 563 If not found and ui.askusername is True, ask the user, else use
564 564 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
565 565 """
566 566 user = os.environ.get("HGUSER")
567 567 if user is None:
568 568 user = self.config("ui", ["username", "user"])
569 569 if user is not None:
570 570 user = os.path.expandvars(user)
571 571 if user is None:
572 572 user = os.environ.get("EMAIL")
573 573 if user is None and self.configbool("ui", "askusername"):
574 574 user = self.prompt(_("enter a commit username:"), default=None)
575 575 if user is None and not self.interactive():
576 576 try:
577 577 user = '%s@%s' % (util.getuser(), socket.getfqdn())
578 578 self.warn(_("no username found, using '%s' instead\n") % user)
579 579 except KeyError:
580 580 pass
581 581 if not user:
582 582 raise error.Abort(_('no username supplied'),
583 583 hint=_('use "hg config --edit" '
584 584 'to set your username'))
585 585 if "\n" in user:
586 586 raise error.Abort(_("username %s contains a newline\n")
587 587 % repr(user))
588 588 return user
589 589
590 590 def shortuser(self, user):
591 591 """Return a short representation of a user name or email address."""
592 592 if not self.verbose:
593 593 user = util.shortuser(user)
594 594 return user
595 595
596 596 def expandpath(self, loc, default=None):
597 597 """Return repository location relative to cwd or from [paths]"""
598 598 try:
599 599 p = self.paths.getpath(loc)
600 600 if p:
601 601 return p.rawloc
602 602 except error.RepoError:
603 603 pass
604 604
605 605 if default:
606 606 try:
607 607 p = self.paths.getpath(default)
608 608 if p:
609 609 return p.rawloc
610 610 except error.RepoError:
611 611 pass
612 612
613 613 return loc
614 614
615 615 @util.propertycache
616 616 def paths(self):
617 617 return paths(self)
618 618
619 619 def pushbuffer(self, error=False, subproc=False, labeled=False):
620 620 """install a buffer to capture standard output of the ui object
621 621
622 622 If error is True, the error output will be captured too.
623 623
624 624 If subproc is True, output from subprocesses (typically hooks) will be
625 625 captured too.
626 626
627 627 If labeled is True, any labels associated with buffered
628 628 output will be handled. By default, this has no effect
629 629 on the output returned, but extensions and GUI tools may
630 630 handle this argument and returned styled output. If output
631 631 is being buffered so it can be captured and parsed or
632 632 processed, labeled should not be set to True.
633 633 """
634 634 self._buffers.append([])
635 635 self._bufferstates.append((error, subproc, labeled))
636 636 self._bufferapplylabels = labeled
637 637
638 638 def popbuffer(self):
639 639 '''pop the last buffer and return the buffered output'''
640 640 self._bufferstates.pop()
641 641 if self._bufferstates:
642 642 self._bufferapplylabels = self._bufferstates[-1][2]
643 643 else:
644 644 self._bufferapplylabels = None
645 645
646 646 return "".join(self._buffers.pop())
647 647
648 648 def write(self, *args, **opts):
649 649 '''write args to output
650 650
651 651 By default, this method simply writes to the buffer or stdout,
652 652 but extensions or GUI tools may override this method,
653 653 write_err(), popbuffer(), and label() to style output from
654 654 various parts of hg.
655 655
656 656 An optional keyword argument, "label", can be passed in.
657 657 This should be a string containing label names separated by
658 658 space. Label names take the form of "topic.type". For example,
659 659 ui.debug() issues a label of "ui.debug".
660 660
661 661 When labeling output for a specific command, a label of
662 662 "cmdname.type" is recommended. For example, status issues
663 663 a label of "status.modified" for modified files.
664 664 '''
665 if self._buffers:
665 if self._buffers and not opts.get('prompt', False):
666 666 self._buffers[-1].extend(a for a in args)
667 667 else:
668 668 self._progclear()
669 669 for a in args:
670 670 self.fout.write(a)
671 671
672 672 def write_err(self, *args, **opts):
673 673 self._progclear()
674 674 try:
675 675 if self._bufferstates and self._bufferstates[-1][0]:
676 676 return self.write(*args, **opts)
677 677 if not getattr(self.fout, 'closed', False):
678 678 self.fout.flush()
679 679 for a in args:
680 680 self.ferr.write(a)
681 681 # stderr may be buffered under win32 when redirected to files,
682 682 # including stdout.
683 683 if not getattr(self.ferr, 'closed', False):
684 684 self.ferr.flush()
685 685 except IOError as inst:
686 686 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
687 687 raise
688 688
689 689 def flush(self):
690 690 try: self.fout.flush()
691 691 except (IOError, ValueError): pass
692 692 try: self.ferr.flush()
693 693 except (IOError, ValueError): pass
694 694
695 695 def _isatty(self, fh):
696 696 if self.configbool('ui', 'nontty', False):
697 697 return False
698 698 return util.isatty(fh)
699 699
700 700 def interface(self, feature):
701 701 """what interface to use for interactive console features?
702 702
703 703 The interface is controlled by the value of `ui.interface` but also by
704 704 the value of feature-specific configuration. For example:
705 705
706 706 ui.interface.histedit = text
707 707 ui.interface.chunkselector = curses
708 708
709 709 Here the features are "histedit" and "chunkselector".
710 710
711 711 The configuration above means that the default interfaces for commands
712 712 is curses, the interface for histedit is text and the interface for
713 713 selecting chunk is crecord (the best curses interface available).
714 714
715 715 Consider the following exemple:
716 716 ui.interface = curses
717 717 ui.interface.histedit = text
718 718
719 719 Then histedit will use the text interface and chunkselector will use
720 720 the default curses interface (crecord at the moment).
721 721 """
722 722 alldefaults = frozenset(["text", "curses"])
723 723
724 724 featureinterfaces = {
725 725 "chunkselector": [
726 726 "text",
727 727 "curses",
728 728 ]
729 729 }
730 730
731 731 # Feature-specific interface
732 732 if feature not in featureinterfaces.keys():
733 733 # Programming error, not user error
734 734 raise ValueError("Unknown feature requested %s" % feature)
735 735
736 736 availableinterfaces = frozenset(featureinterfaces[feature])
737 737 if alldefaults > availableinterfaces:
738 738 # Programming error, not user error. We need a use case to
739 739 # define the right thing to do here.
740 740 raise ValueError(
741 741 "Feature %s does not handle all default interfaces" %
742 742 feature)
743 743
744 744 if self.plain():
745 745 return "text"
746 746
747 747 # Default interface for all the features
748 748 defaultinterface = "text"
749 749 i = self.config("ui", "interface", None)
750 750 if i in alldefaults:
751 751 defaultinterface = i
752 752
753 753 choseninterface = defaultinterface
754 754 f = self.config("ui", "interface.%s" % feature, None)
755 755 if f in availableinterfaces:
756 756 choseninterface = f
757 757
758 758 if i is not None and defaultinterface != i:
759 759 if f is not None:
760 760 self.warn(_("invalid value for ui.interface: %s\n") %
761 761 (i,))
762 762 else:
763 763 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
764 764 (i, choseninterface))
765 765 if f is not None and choseninterface != f:
766 766 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
767 767 (feature, f, choseninterface))
768 768
769 769 return choseninterface
770 770
771 771 def interactive(self):
772 772 '''is interactive input allowed?
773 773
774 774 An interactive session is a session where input can be reasonably read
775 775 from `sys.stdin'. If this function returns false, any attempt to read
776 776 from stdin should fail with an error, unless a sensible default has been
777 777 specified.
778 778
779 779 Interactiveness is triggered by the value of the `ui.interactive'
780 780 configuration variable or - if it is unset - when `sys.stdin' points
781 781 to a terminal device.
782 782
783 783 This function refers to input only; for output, see `ui.formatted()'.
784 784 '''
785 785 i = self.configbool("ui", "interactive", None)
786 786 if i is None:
787 787 # some environments replace stdin without implementing isatty
788 788 # usually those are non-interactive
789 789 return self._isatty(self.fin)
790 790
791 791 return i
792 792
793 793 def termwidth(self):
794 794 '''how wide is the terminal in columns?
795 795 '''
796 796 if 'COLUMNS' in os.environ:
797 797 try:
798 798 return int(os.environ['COLUMNS'])
799 799 except ValueError:
800 800 pass
801 801 return util.termwidth()
802 802
803 803 def formatted(self):
804 804 '''should formatted output be used?
805 805
806 806 It is often desirable to format the output to suite the output medium.
807 807 Examples of this are truncating long lines or colorizing messages.
808 808 However, this is not often not desirable when piping output into other
809 809 utilities, e.g. `grep'.
810 810
811 811 Formatted output is triggered by the value of the `ui.formatted'
812 812 configuration variable or - if it is unset - when `sys.stdout' points
813 813 to a terminal device. Please note that `ui.formatted' should be
814 814 considered an implementation detail; it is not intended for use outside
815 815 Mercurial or its extensions.
816 816
817 817 This function refers to output only; for input, see `ui.interactive()'.
818 818 This function always returns false when in plain mode, see `ui.plain()'.
819 819 '''
820 820 if self.plain():
821 821 return False
822 822
823 823 i = self.configbool("ui", "formatted", None)
824 824 if i is None:
825 825 # some environments replace stdout without implementing isatty
826 826 # usually those are non-interactive
827 827 return self._isatty(self.fout)
828 828
829 829 return i
830 830
831 831 def _readline(self, prompt=''):
832 832 if self._isatty(self.fin):
833 833 try:
834 834 # magically add command line editing support, where
835 835 # available
836 836 import readline
837 837 # force demandimport to really load the module
838 838 readline.read_history_file
839 839 # windows sometimes raises something other than ImportError
840 840 except Exception:
841 841 pass
842 842
843 843 # call write() so output goes through subclassed implementation
844 844 # e.g. color extension on Windows
845 self.write(prompt)
845 self.write(prompt, prompt=True)
846 846
847 847 # instead of trying to emulate raw_input, swap (self.fin,
848 848 # self.fout) with (sys.stdin, sys.stdout)
849 849 oldin = sys.stdin
850 850 oldout = sys.stdout
851 851 sys.stdin = self.fin
852 852 sys.stdout = self.fout
853 853 # prompt ' ' must exist; otherwise readline may delete entire line
854 854 # - http://bugs.python.org/issue12833
855 855 line = raw_input(' ')
856 856 sys.stdin = oldin
857 857 sys.stdout = oldout
858 858
859 859 # When stdin is in binary mode on Windows, it can cause
860 860 # raw_input() to emit an extra trailing carriage return
861 861 if os.linesep == '\r\n' and line and line[-1] == '\r':
862 862 line = line[:-1]
863 863 return line
864 864
865 865 def prompt(self, msg, default="y"):
866 866 """Prompt user with msg, read response.
867 867 If ui is not interactive, the default is returned.
868 868 """
869 869 if not self.interactive():
870 870 self.write(msg, ' ', default or '', "\n")
871 871 return default
872 872 try:
873 873 r = self._readline(self.label(msg, 'ui.prompt'))
874 874 if not r:
875 875 r = default
876 876 if self.configbool('ui', 'promptecho'):
877 877 self.write(r, "\n")
878 878 return r
879 879 except EOFError:
880 880 raise error.ResponseExpected()
881 881
882 882 @staticmethod
883 883 def extractchoices(prompt):
884 884 """Extract prompt message and list of choices from specified prompt.
885 885
886 886 This returns tuple "(message, choices)", and "choices" is the
887 887 list of tuple "(response character, text without &)".
888 888
889 889 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
890 890 ('awake? ', [('y', 'Yes'), ('n', 'No')])
891 891 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
892 892 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
893 893 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
894 894 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
895 895 """
896 896
897 897 # Sadly, the prompt string may have been built with a filename
898 898 # containing "$$" so let's try to find the first valid-looking
899 899 # prompt to start parsing. Sadly, we also can't rely on
900 900 # choices containing spaces, ASCII, or basically anything
901 901 # except an ampersand followed by a character.
902 902 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
903 903 msg = m.group(1)
904 904 choices = [p.strip(' ') for p in m.group(2).split('$$')]
905 905 return (msg,
906 906 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
907 907 for s in choices])
908 908
909 909 def promptchoice(self, prompt, default=0):
910 910 """Prompt user with a message, read response, and ensure it matches
911 911 one of the provided choices. The prompt is formatted as follows:
912 912
913 913 "would you like fries with that (Yn)? $$ &Yes $$ &No"
914 914
915 915 The index of the choice is returned. Responses are case
916 916 insensitive. If ui is not interactive, the default is
917 917 returned.
918 918 """
919 919
920 920 msg, choices = self.extractchoices(prompt)
921 921 resps = [r for r, t in choices]
922 922 while True:
923 923 r = self.prompt(msg, resps[default])
924 924 if r.lower() in resps:
925 925 return resps.index(r.lower())
926 926 self.write(_("unrecognized response\n"))
927 927
928 928 def getpass(self, prompt=None, default=None):
929 929 if not self.interactive():
930 930 return default
931 931 try:
932 932 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
933 933 # disable getpass() only if explicitly specified. it's still valid
934 934 # to interact with tty even if fin is not a tty.
935 935 if self.configbool('ui', 'nontty'):
936 936 return self.fin.readline().rstrip('\n')
937 937 else:
938 938 return getpass.getpass('')
939 939 except EOFError:
940 940 raise error.ResponseExpected()
941 941 def status(self, *msg, **opts):
942 942 '''write status message to output (if ui.quiet is False)
943 943
944 944 This adds an output label of "ui.status".
945 945 '''
946 946 if not self.quiet:
947 947 opts['label'] = opts.get('label', '') + ' ui.status'
948 948 self.write(*msg, **opts)
949 949 def warn(self, *msg, **opts):
950 950 '''write warning message to output (stderr)
951 951
952 952 This adds an output label of "ui.warning".
953 953 '''
954 954 opts['label'] = opts.get('label', '') + ' ui.warning'
955 955 self.write_err(*msg, **opts)
956 956 def note(self, *msg, **opts):
957 957 '''write note to output (if ui.verbose is True)
958 958
959 959 This adds an output label of "ui.note".
960 960 '''
961 961 if self.verbose:
962 962 opts['label'] = opts.get('label', '') + ' ui.note'
963 963 self.write(*msg, **opts)
964 964 def debug(self, *msg, **opts):
965 965 '''write debug message to output (if ui.debugflag is True)
966 966
967 967 This adds an output label of "ui.debug".
968 968 '''
969 969 if self.debugflag:
970 970 opts['label'] = opts.get('label', '') + ' ui.debug'
971 971 self.write(*msg, **opts)
972 972
973 973 def edit(self, text, user, extra=None, editform=None, pending=None):
974 974 extra_defaults = { 'prefix': 'editor' }
975 975 if extra is not None:
976 976 extra_defaults.update(extra)
977 977 extra = extra_defaults
978 978 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
979 979 suffix=".txt", text=True)
980 980 try:
981 981 f = os.fdopen(fd, "w")
982 982 f.write(text)
983 983 f.close()
984 984
985 985 environ = {'HGUSER': user}
986 986 if 'transplant_source' in extra:
987 987 environ.update({'HGREVISION': hex(extra['transplant_source'])})
988 988 for label in ('intermediate-source', 'source', 'rebase_source'):
989 989 if label in extra:
990 990 environ.update({'HGREVISION': extra[label]})
991 991 break
992 992 if editform:
993 993 environ.update({'HGEDITFORM': editform})
994 994 if pending:
995 995 environ.update({'HG_PENDING': pending})
996 996
997 997 editor = self.geteditor()
998 998
999 999 self.system("%s \"%s\"" % (editor, name),
1000 1000 environ=environ,
1001 1001 onerr=error.Abort, errprefix=_("edit failed"))
1002 1002
1003 1003 f = open(name)
1004 1004 t = f.read()
1005 1005 f.close()
1006 1006 finally:
1007 1007 os.unlink(name)
1008 1008
1009 1009 return t
1010 1010
1011 1011 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
1012 1012 '''execute shell command with appropriate output stream. command
1013 1013 output will be redirected if fout is not stdout.
1014 1014 '''
1015 1015 out = self.fout
1016 1016 if any(s[1] for s in self._bufferstates):
1017 1017 out = self
1018 1018 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1019 1019 errprefix=errprefix, out=out)
1020 1020
1021 1021 def traceback(self, exc=None, force=False):
1022 1022 '''print exception traceback if traceback printing enabled or forced.
1023 1023 only to call in exception handler. returns true if traceback
1024 1024 printed.'''
1025 1025 if self.tracebackflag or force:
1026 1026 if exc is None:
1027 1027 exc = sys.exc_info()
1028 1028 cause = getattr(exc[1], 'cause', None)
1029 1029
1030 1030 if cause is not None:
1031 1031 causetb = traceback.format_tb(cause[2])
1032 1032 exctb = traceback.format_tb(exc[2])
1033 1033 exconly = traceback.format_exception_only(cause[0], cause[1])
1034 1034
1035 1035 # exclude frame where 'exc' was chained and rethrown from exctb
1036 1036 self.write_err('Traceback (most recent call last):\n',
1037 1037 ''.join(exctb[:-1]),
1038 1038 ''.join(causetb),
1039 1039 ''.join(exconly))
1040 1040 else:
1041 1041 output = traceback.format_exception(exc[0], exc[1], exc[2])
1042 1042 self.write_err(''.join(output))
1043 1043 return self.tracebackflag or force
1044 1044
1045 1045 def geteditor(self):
1046 1046 '''return editor to use'''
1047 1047 if sys.platform == 'plan9':
1048 1048 # vi is the MIPS instruction simulator on Plan 9. We
1049 1049 # instead default to E to plumb commit messages to
1050 1050 # avoid confusion.
1051 1051 editor = 'E'
1052 1052 else:
1053 1053 editor = 'vi'
1054 1054 return (os.environ.get("HGEDITOR") or
1055 1055 self.config("ui", "editor") or
1056 1056 os.environ.get("VISUAL") or
1057 1057 os.environ.get("EDITOR", editor))
1058 1058
1059 1059 @util.propertycache
1060 1060 def _progbar(self):
1061 1061 """setup the progbar singleton to the ui object"""
1062 1062 if (self.quiet or self.debugflag
1063 1063 or self.configbool('progress', 'disable', False)
1064 1064 or not progress.shouldprint(self)):
1065 1065 return None
1066 1066 return getprogbar(self)
1067 1067
1068 1068 def _progclear(self):
1069 1069 """clear progress bar output if any. use it before any output"""
1070 1070 if '_progbar' not in vars(self): # nothing loaded yet
1071 1071 return
1072 1072 if self._progbar is not None and self._progbar.printed:
1073 1073 self._progbar.clear()
1074 1074
1075 1075 def progress(self, topic, pos, item="", unit="", total=None):
1076 1076 '''show a progress message
1077 1077
1078 1078 By default a textual progress bar will be displayed if an operation
1079 1079 takes too long. 'topic' is the current operation, 'item' is a
1080 1080 non-numeric marker of the current position (i.e. the currently
1081 1081 in-process file), 'pos' is the current numeric position (i.e.
1082 1082 revision, bytes, etc.), unit is a corresponding unit label,
1083 1083 and total is the highest expected pos.
1084 1084
1085 1085 Multiple nested topics may be active at a time.
1086 1086
1087 1087 All topics should be marked closed by setting pos to None at
1088 1088 termination.
1089 1089 '''
1090 1090 if self._progbar is not None:
1091 1091 self._progbar.progress(topic, pos, item=item, unit=unit,
1092 1092 total=total)
1093 1093 if pos is None or not self.configbool('progress', 'debug'):
1094 1094 return
1095 1095
1096 1096 if unit:
1097 1097 unit = ' ' + unit
1098 1098 if item:
1099 1099 item = ' ' + item
1100 1100
1101 1101 if total:
1102 1102 pct = 100.0 * pos / total
1103 1103 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1104 1104 % (topic, item, pos, total, unit, pct))
1105 1105 else:
1106 1106 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1107 1107
1108 1108 def log(self, service, *msg, **opts):
1109 1109 '''hook for logging facility extensions
1110 1110
1111 1111 service should be a readily-identifiable subsystem, which will
1112 1112 allow filtering.
1113 1113
1114 1114 *msg should be a newline-terminated format string to log, and
1115 1115 then any values to %-format into that format string.
1116 1116
1117 1117 **opts currently has no defined meanings.
1118 1118 '''
1119 1119
1120 1120 def label(self, msg, label):
1121 1121 '''style msg based on supplied label
1122 1122
1123 1123 Like ui.write(), this just returns msg unchanged, but extensions
1124 1124 and GUI tools can override it to allow styling output without
1125 1125 writing it.
1126 1126
1127 1127 ui.write(s, 'label') is equivalent to
1128 1128 ui.write(ui.label(s, 'label')).
1129 1129 '''
1130 1130 return msg
1131 1131
1132 1132 def develwarn(self, msg, stacklevel=1):
1133 1133 """issue a developer warning message
1134 1134
1135 1135 Use 'stacklevel' to report the offender some layers further up in the
1136 1136 stack.
1137 1137 """
1138 1138 msg = 'devel-warn: ' + msg
1139 1139 stacklevel += 1 # get in develwarn
1140 1140 if self.tracebackflag:
1141 1141 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1142 1142 self.log('develwarn', '%s at:\n%s' %
1143 1143 (msg, ''.join(util.getstackframes(stacklevel))))
1144 1144 else:
1145 1145 curframe = inspect.currentframe()
1146 1146 calframe = inspect.getouterframes(curframe, 2)
1147 1147 self.write_err('%s at: %s:%s (%s)\n'
1148 1148 % ((msg,) + calframe[stacklevel][1:4]))
1149 1149 self.log('develwarn', '%s at: %s:%s (%s)\n',
1150 1150 msg, *calframe[stacklevel][1:4])
1151 1151
1152 1152 def deprecwarn(self, msg, version):
1153 1153 """issue a deprecation warning
1154 1154
1155 1155 - msg: message explaining what is deprecated and how to upgrade,
1156 1156 - version: last version where the API will be supported,
1157 1157 """
1158 1158 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1159 1159 " update your code.)") % version
1160 1160 self.develwarn(msg, stacklevel=2)
1161 1161
1162 1162 class paths(dict):
1163 1163 """Represents a collection of paths and their configs.
1164 1164
1165 1165 Data is initially derived from ui instances and the config files they have
1166 1166 loaded.
1167 1167 """
1168 1168 def __init__(self, ui):
1169 1169 dict.__init__(self)
1170 1170
1171 1171 for name, loc in ui.configitems('paths', ignoresub=True):
1172 1172 # No location is the same as not existing.
1173 1173 if not loc:
1174 1174 continue
1175 1175 loc, sub = ui.configsuboptions('paths', name)
1176 1176 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1177 1177
1178 1178 def getpath(self, name, default=None):
1179 1179 """Return a ``path`` from a string, falling back to default.
1180 1180
1181 1181 ``name`` can be a named path or locations. Locations are filesystem
1182 1182 paths or URIs.
1183 1183
1184 1184 Returns None if ``name`` is not a registered path, a URI, or a local
1185 1185 path to a repo.
1186 1186 """
1187 1187 # Only fall back to default if no path was requested.
1188 1188 if name is None:
1189 1189 if not default:
1190 1190 default = ()
1191 1191 elif not isinstance(default, (tuple, list)):
1192 1192 default = (default,)
1193 1193 for k in default:
1194 1194 try:
1195 1195 return self[k]
1196 1196 except KeyError:
1197 1197 continue
1198 1198 return None
1199 1199
1200 1200 # Most likely empty string.
1201 1201 # This may need to raise in the future.
1202 1202 if not name:
1203 1203 return None
1204 1204
1205 1205 try:
1206 1206 return self[name]
1207 1207 except KeyError:
1208 1208 # Try to resolve as a local path or URI.
1209 1209 try:
1210 1210 # We don't pass sub-options in, so no need to pass ui instance.
1211 1211 return path(None, None, rawloc=name)
1212 1212 except ValueError:
1213 1213 raise error.RepoError(_('repository %s does not exist') %
1214 1214 name)
1215 1215
1216 1216 _pathsuboptions = {}
1217 1217
1218 1218 def pathsuboption(option, attr):
1219 1219 """Decorator used to declare a path sub-option.
1220 1220
1221 1221 Arguments are the sub-option name and the attribute it should set on
1222 1222 ``path`` instances.
1223 1223
1224 1224 The decorated function will receive as arguments a ``ui`` instance,
1225 1225 ``path`` instance, and the string value of this option from the config.
1226 1226 The function should return the value that will be set on the ``path``
1227 1227 instance.
1228 1228
1229 1229 This decorator can be used to perform additional verification of
1230 1230 sub-options and to change the type of sub-options.
1231 1231 """
1232 1232 def register(func):
1233 1233 _pathsuboptions[option] = (attr, func)
1234 1234 return func
1235 1235 return register
1236 1236
1237 1237 @pathsuboption('pushurl', 'pushloc')
1238 1238 def pushurlpathoption(ui, path, value):
1239 1239 u = util.url(value)
1240 1240 # Actually require a URL.
1241 1241 if not u.scheme:
1242 1242 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1243 1243 return None
1244 1244
1245 1245 # Don't support the #foo syntax in the push URL to declare branch to
1246 1246 # push.
1247 1247 if u.fragment:
1248 1248 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1249 1249 'ignoring)\n') % path.name)
1250 1250 u.fragment = None
1251 1251
1252 1252 return str(u)
1253 1253
1254 1254 class path(object):
1255 1255 """Represents an individual path and its configuration."""
1256 1256
1257 1257 def __init__(self, ui, name, rawloc=None, suboptions=None):
1258 1258 """Construct a path from its config options.
1259 1259
1260 1260 ``ui`` is the ``ui`` instance the path is coming from.
1261 1261 ``name`` is the symbolic name of the path.
1262 1262 ``rawloc`` is the raw location, as defined in the config.
1263 1263 ``pushloc`` is the raw locations pushes should be made to.
1264 1264
1265 1265 If ``name`` is not defined, we require that the location be a) a local
1266 1266 filesystem path with a .hg directory or b) a URL. If not,
1267 1267 ``ValueError`` is raised.
1268 1268 """
1269 1269 if not rawloc:
1270 1270 raise ValueError('rawloc must be defined')
1271 1271
1272 1272 # Locations may define branches via syntax <base>#<branch>.
1273 1273 u = util.url(rawloc)
1274 1274 branch = None
1275 1275 if u.fragment:
1276 1276 branch = u.fragment
1277 1277 u.fragment = None
1278 1278
1279 1279 self.url = u
1280 1280 self.branch = branch
1281 1281
1282 1282 self.name = name
1283 1283 self.rawloc = rawloc
1284 1284 self.loc = str(u)
1285 1285
1286 1286 # When given a raw location but not a symbolic name, validate the
1287 1287 # location is valid.
1288 1288 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1289 1289 raise ValueError('location is not a URL or path to a local '
1290 1290 'repo: %s' % rawloc)
1291 1291
1292 1292 suboptions = suboptions or {}
1293 1293
1294 1294 # Now process the sub-options. If a sub-option is registered, its
1295 1295 # attribute will always be present. The value will be None if there
1296 1296 # was no valid sub-option.
1297 1297 for suboption, (attr, func) in _pathsuboptions.iteritems():
1298 1298 if suboption not in suboptions:
1299 1299 setattr(self, attr, None)
1300 1300 continue
1301 1301
1302 1302 value = func(ui, self, suboptions[suboption])
1303 1303 setattr(self, attr, value)
1304 1304
1305 1305 def _isvalidlocalpath(self, path):
1306 1306 """Returns True if the given path is a potentially valid repository.
1307 1307 This is its own function so that extensions can change the definition of
1308 1308 'valid' in this case (like when pulling from a git repo into a hg
1309 1309 one)."""
1310 1310 return os.path.isdir(os.path.join(path, '.hg'))
1311 1311
1312 1312 @property
1313 1313 def suboptions(self):
1314 1314 """Return sub-options and their values for this path.
1315 1315
1316 1316 This is intended to be used for presentation purposes.
1317 1317 """
1318 1318 d = {}
1319 1319 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1320 1320 value = getattr(self, attr)
1321 1321 if value is not None:
1322 1322 d[subopt] = value
1323 1323 return d
1324 1324
1325 1325 # we instantiate one globally shared progress bar to avoid
1326 1326 # competing progress bars when multiple UI objects get created
1327 1327 _progresssingleton = None
1328 1328
1329 1329 def getprogbar(ui):
1330 1330 global _progresssingleton
1331 1331 if _progresssingleton is None:
1332 1332 # passing 'ui' object to the singleton is fishy,
1333 1333 # this is how the extension used to work but feel free to rework it.
1334 1334 _progresssingleton = progress.progbar(ui)
1335 1335 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now