##// END OF EJS Templates
ui: move pre/post processes from low-level write()s to _writenobuf()...
Yuya Nishihara -
r40575:04a9dd8d default
parent child Browse files
Show More
@@ -1,1923 +1,1921 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.fout, *args, **opts)
951 951
952 952 def _writenobuf(self, dest, *args, **opts):
953 953 self._progclear()
954 954 if dest is self.fout:
955 955 write = self._write
956 956 elif dest is self.ferr:
957 957 write = self._write_err
958 958 else:
959 959 raise error.ProgrammingError('unsupported file to write')
960 960 msg = b''.join(args)
961 961
962 962 # opencode timeblockedsection because this is a critical path
963 963 starttime = util.timer()
964 964 try:
965 if dest is self.ferr and not getattr(self.fout, 'closed', False):
966 self.fout.flush()
965 967 if self._colormode == 'win32':
966 968 # windows color printing is its own can of crab, defer to
967 969 # the color module and that is it.
968 970 color.win32print(self, write, msg, **opts)
969 971 else:
970 972 if self._colormode is not None:
971 973 label = opts.get(r'label', '')
972 974 msg = self.label(msg, label)
973 975 write(msg)
976 # stderr may be buffered under win32 when redirected to files,
977 # including stdout.
978 if dest is self.ferr and not getattr(self.ferr, 'closed', False):
979 dest.flush()
980 except IOError as err:
981 if (dest is self.ferr
982 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
983 # no way to report the error, so ignore it
984 return
985 raise error.StdioError(err)
974 986 finally:
975 987 self._blockedtimes['stdio_blocked'] += \
976 988 (util.timer() - starttime) * 1000
977 989
978 990 def _write(self, data):
979 try:
980 self.fout.write(data)
981 except IOError as err:
982 raise error.StdioError(err)
991 self.fout.write(data)
983 992
984 993 def write_err(self, *args, **opts):
985 994 if self._bufferstates and self._bufferstates[-1][0]:
986 995 self.write(*args, **opts)
987 996 else:
988 997 self._writenobuf(self.ferr, *args, **opts)
989 998
990 999 def _write_err(self, data):
991 try:
992 if True:
993 if not getattr(self.fout, 'closed', False):
994 self.fout.flush()
995 self.ferr.write(data)
996 # stderr may be buffered under win32 when redirected to files,
997 # including stdout.
998 if not getattr(self.ferr, 'closed', False):
999 self.ferr.flush()
1000 except IOError as inst:
1001 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1002 raise error.StdioError(inst)
1000 self.ferr.write(data)
1003 1001
1004 1002 def flush(self):
1005 1003 # opencode timeblockedsection because this is a critical path
1006 1004 starttime = util.timer()
1007 1005 try:
1008 1006 try:
1009 1007 self.fout.flush()
1010 1008 except IOError as err:
1011 1009 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1012 1010 raise error.StdioError(err)
1013 1011 finally:
1014 1012 try:
1015 1013 self.ferr.flush()
1016 1014 except IOError as err:
1017 1015 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1018 1016 raise error.StdioError(err)
1019 1017 finally:
1020 1018 self._blockedtimes['stdio_blocked'] += \
1021 1019 (util.timer() - starttime) * 1000
1022 1020
1023 1021 def _isatty(self, fh):
1024 1022 if self.configbool('ui', 'nontty'):
1025 1023 return False
1026 1024 return procutil.isatty(fh)
1027 1025
1028 1026 def disablepager(self):
1029 1027 self._disablepager = True
1030 1028
1031 1029 def pager(self, command):
1032 1030 """Start a pager for subsequent command output.
1033 1031
1034 1032 Commands which produce a long stream of output should call
1035 1033 this function to activate the user's preferred pagination
1036 1034 mechanism (which may be no pager). Calling this function
1037 1035 precludes any future use of interactive functionality, such as
1038 1036 prompting the user or activating curses.
1039 1037
1040 1038 Args:
1041 1039 command: The full, non-aliased name of the command. That is, "log"
1042 1040 not "history, "summary" not "summ", etc.
1043 1041 """
1044 1042 if (self._disablepager
1045 1043 or self.pageractive):
1046 1044 # how pager should do is already determined
1047 1045 return
1048 1046
1049 1047 if not command.startswith('internal-always-') and (
1050 1048 # explicit --pager=on (= 'internal-always-' prefix) should
1051 1049 # take precedence over disabling factors below
1052 1050 command in self.configlist('pager', 'ignore')
1053 1051 or not self.configbool('ui', 'paginate')
1054 1052 or not self.configbool('pager', 'attend-' + command, True)
1055 1053 or encoding.environ.get('TERM') == 'dumb'
1056 1054 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1057 1055 # formatted() will need some adjustment.
1058 1056 or not self.formatted()
1059 1057 or self.plain()
1060 1058 or self._buffers
1061 1059 # TODO: expose debugger-enabled on the UI object
1062 1060 or '--debugger' in pycompat.sysargv):
1063 1061 # We only want to paginate if the ui appears to be
1064 1062 # interactive, the user didn't say HGPLAIN or
1065 1063 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1066 1064 return
1067 1065
1068 1066 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1069 1067 if not pagercmd:
1070 1068 return
1071 1069
1072 1070 pagerenv = {}
1073 1071 for name, value in rcutil.defaultpagerenv().items():
1074 1072 if name not in encoding.environ:
1075 1073 pagerenv[name] = value
1076 1074
1077 1075 self.debug('starting pager for command %s\n' %
1078 1076 stringutil.pprint(command))
1079 1077 self.flush()
1080 1078
1081 1079 wasformatted = self.formatted()
1082 1080 if util.safehasattr(signal, "SIGPIPE"):
1083 1081 signal.signal(signal.SIGPIPE, _catchterm)
1084 1082 if self._runpager(pagercmd, pagerenv):
1085 1083 self.pageractive = True
1086 1084 # Preserve the formatted-ness of the UI. This is important
1087 1085 # because we mess with stdout, which might confuse
1088 1086 # auto-detection of things being formatted.
1089 1087 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1090 1088 self.setconfig('ui', 'interactive', False, 'pager')
1091 1089
1092 1090 # If pagermode differs from color.mode, reconfigure color now that
1093 1091 # pageractive is set.
1094 1092 cm = self._colormode
1095 1093 if cm != self.config('color', 'pagermode', cm):
1096 1094 color.setup(self)
1097 1095 else:
1098 1096 # If the pager can't be spawned in dispatch when --pager=on is
1099 1097 # given, don't try again when the command runs, to avoid a duplicate
1100 1098 # warning about a missing pager command.
1101 1099 self.disablepager()
1102 1100
1103 1101 def _runpager(self, command, env=None):
1104 1102 """Actually start the pager and set up file descriptors.
1105 1103
1106 1104 This is separate in part so that extensions (like chg) can
1107 1105 override how a pager is invoked.
1108 1106 """
1109 1107 if command == 'cat':
1110 1108 # Save ourselves some work.
1111 1109 return False
1112 1110 # If the command doesn't contain any of these characters, we
1113 1111 # assume it's a binary and exec it directly. This means for
1114 1112 # simple pager command configurations, we can degrade
1115 1113 # gracefully and tell the user about their broken pager.
1116 1114 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1117 1115
1118 1116 if pycompat.iswindows and not shell:
1119 1117 # Window's built-in `more` cannot be invoked with shell=False, but
1120 1118 # its `more.com` can. Hide this implementation detail from the
1121 1119 # user so we can also get sane bad PAGER behavior. MSYS has
1122 1120 # `more.exe`, so do a cmd.exe style resolution of the executable to
1123 1121 # determine which one to use.
1124 1122 fullcmd = procutil.findexe(command)
1125 1123 if not fullcmd:
1126 1124 self.warn(_("missing pager command '%s', skipping pager\n")
1127 1125 % command)
1128 1126 return False
1129 1127
1130 1128 command = fullcmd
1131 1129
1132 1130 try:
1133 1131 pager = subprocess.Popen(
1134 1132 procutil.tonativestr(command), shell=shell, bufsize=-1,
1135 1133 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1136 1134 stdout=procutil.stdout, stderr=procutil.stderr,
1137 1135 env=procutil.tonativeenv(procutil.shellenviron(env)))
1138 1136 except OSError as e:
1139 1137 if e.errno == errno.ENOENT and not shell:
1140 1138 self.warn(_("missing pager command '%s', skipping pager\n")
1141 1139 % command)
1142 1140 return False
1143 1141 raise
1144 1142
1145 1143 # back up original file descriptors
1146 1144 stdoutfd = os.dup(procutil.stdout.fileno())
1147 1145 stderrfd = os.dup(procutil.stderr.fileno())
1148 1146
1149 1147 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1150 1148 if self._isatty(procutil.stderr):
1151 1149 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1152 1150
1153 1151 @self.atexit
1154 1152 def killpager():
1155 1153 if util.safehasattr(signal, "SIGINT"):
1156 1154 signal.signal(signal.SIGINT, signal.SIG_IGN)
1157 1155 # restore original fds, closing pager.stdin copies in the process
1158 1156 os.dup2(stdoutfd, procutil.stdout.fileno())
1159 1157 os.dup2(stderrfd, procutil.stderr.fileno())
1160 1158 pager.stdin.close()
1161 1159 pager.wait()
1162 1160
1163 1161 return True
1164 1162
1165 1163 @property
1166 1164 def _exithandlers(self):
1167 1165 return _reqexithandlers
1168 1166
1169 1167 def atexit(self, func, *args, **kwargs):
1170 1168 '''register a function to run after dispatching a request
1171 1169
1172 1170 Handlers do not stay registered across request boundaries.'''
1173 1171 self._exithandlers.append((func, args, kwargs))
1174 1172 return func
1175 1173
1176 1174 def interface(self, feature):
1177 1175 """what interface to use for interactive console features?
1178 1176
1179 1177 The interface is controlled by the value of `ui.interface` but also by
1180 1178 the value of feature-specific configuration. For example:
1181 1179
1182 1180 ui.interface.histedit = text
1183 1181 ui.interface.chunkselector = curses
1184 1182
1185 1183 Here the features are "histedit" and "chunkselector".
1186 1184
1187 1185 The configuration above means that the default interfaces for commands
1188 1186 is curses, the interface for histedit is text and the interface for
1189 1187 selecting chunk is crecord (the best curses interface available).
1190 1188
1191 1189 Consider the following example:
1192 1190 ui.interface = curses
1193 1191 ui.interface.histedit = text
1194 1192
1195 1193 Then histedit will use the text interface and chunkselector will use
1196 1194 the default curses interface (crecord at the moment).
1197 1195 """
1198 1196 alldefaults = frozenset(["text", "curses"])
1199 1197
1200 1198 featureinterfaces = {
1201 1199 "chunkselector": [
1202 1200 "text",
1203 1201 "curses",
1204 1202 ]
1205 1203 }
1206 1204
1207 1205 # Feature-specific interface
1208 1206 if feature not in featureinterfaces.keys():
1209 1207 # Programming error, not user error
1210 1208 raise ValueError("Unknown feature requested %s" % feature)
1211 1209
1212 1210 availableinterfaces = frozenset(featureinterfaces[feature])
1213 1211 if alldefaults > availableinterfaces:
1214 1212 # Programming error, not user error. We need a use case to
1215 1213 # define the right thing to do here.
1216 1214 raise ValueError(
1217 1215 "Feature %s does not handle all default interfaces" %
1218 1216 feature)
1219 1217
1220 1218 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1221 1219 return "text"
1222 1220
1223 1221 # Default interface for all the features
1224 1222 defaultinterface = "text"
1225 1223 i = self.config("ui", "interface")
1226 1224 if i in alldefaults:
1227 1225 defaultinterface = i
1228 1226
1229 1227 choseninterface = defaultinterface
1230 1228 f = self.config("ui", "interface.%s" % feature)
1231 1229 if f in availableinterfaces:
1232 1230 choseninterface = f
1233 1231
1234 1232 if i is not None and defaultinterface != i:
1235 1233 if f is not None:
1236 1234 self.warn(_("invalid value for ui.interface: %s\n") %
1237 1235 (i,))
1238 1236 else:
1239 1237 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1240 1238 (i, choseninterface))
1241 1239 if f is not None and choseninterface != f:
1242 1240 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1243 1241 (feature, f, choseninterface))
1244 1242
1245 1243 return choseninterface
1246 1244
1247 1245 def interactive(self):
1248 1246 '''is interactive input allowed?
1249 1247
1250 1248 An interactive session is a session where input can be reasonably read
1251 1249 from `sys.stdin'. If this function returns false, any attempt to read
1252 1250 from stdin should fail with an error, unless a sensible default has been
1253 1251 specified.
1254 1252
1255 1253 Interactiveness is triggered by the value of the `ui.interactive'
1256 1254 configuration variable or - if it is unset - when `sys.stdin' points
1257 1255 to a terminal device.
1258 1256
1259 1257 This function refers to input only; for output, see `ui.formatted()'.
1260 1258 '''
1261 1259 i = self.configbool("ui", "interactive")
1262 1260 if i is None:
1263 1261 # some environments replace stdin without implementing isatty
1264 1262 # usually those are non-interactive
1265 1263 return self._isatty(self.fin)
1266 1264
1267 1265 return i
1268 1266
1269 1267 def termwidth(self):
1270 1268 '''how wide is the terminal in columns?
1271 1269 '''
1272 1270 if 'COLUMNS' in encoding.environ:
1273 1271 try:
1274 1272 return int(encoding.environ['COLUMNS'])
1275 1273 except ValueError:
1276 1274 pass
1277 1275 return scmutil.termsize(self)[0]
1278 1276
1279 1277 def formatted(self):
1280 1278 '''should formatted output be used?
1281 1279
1282 1280 It is often desirable to format the output to suite the output medium.
1283 1281 Examples of this are truncating long lines or colorizing messages.
1284 1282 However, this is not often not desirable when piping output into other
1285 1283 utilities, e.g. `grep'.
1286 1284
1287 1285 Formatted output is triggered by the value of the `ui.formatted'
1288 1286 configuration variable or - if it is unset - when `sys.stdout' points
1289 1287 to a terminal device. Please note that `ui.formatted' should be
1290 1288 considered an implementation detail; it is not intended for use outside
1291 1289 Mercurial or its extensions.
1292 1290
1293 1291 This function refers to output only; for input, see `ui.interactive()'.
1294 1292 This function always returns false when in plain mode, see `ui.plain()'.
1295 1293 '''
1296 1294 if self.plain():
1297 1295 return False
1298 1296
1299 1297 i = self.configbool("ui", "formatted")
1300 1298 if i is None:
1301 1299 # some environments replace stdout without implementing isatty
1302 1300 # usually those are non-interactive
1303 1301 return self._isatty(self.fout)
1304 1302
1305 1303 return i
1306 1304
1307 1305 def _readline(self):
1308 1306 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1309 1307 # because they have to be text streams with *no buffering*. Instead,
1310 1308 # we use rawinput() only if call_readline() will be invoked by
1311 1309 # PyOS_Readline(), so no I/O will be made at Python layer.
1312 1310 usereadline = (self._isatty(self.fin) and self._isatty(self.fout)
1313 1311 and procutil.isstdin(self.fin)
1314 1312 and procutil.isstdout(self.fout))
1315 1313 if usereadline:
1316 1314 try:
1317 1315 # magically add command line editing support, where
1318 1316 # available
1319 1317 import readline
1320 1318 # force demandimport to really load the module
1321 1319 readline.read_history_file
1322 1320 # windows sometimes raises something other than ImportError
1323 1321 except Exception:
1324 1322 usereadline = False
1325 1323
1326 1324 # prompt ' ' must exist; otherwise readline may delete entire line
1327 1325 # - http://bugs.python.org/issue12833
1328 1326 with self.timeblockedsection('stdio'):
1329 1327 if usereadline:
1330 1328 line = encoding.strtolocal(pycompat.rawinput(r' '))
1331 1329 # When stdin is in binary mode on Windows, it can cause
1332 1330 # raw_input() to emit an extra trailing carriage return
1333 1331 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1334 1332 line = line[:-1]
1335 1333 else:
1336 1334 self.fout.write(b' ')
1337 1335 self.fout.flush()
1338 1336 line = self.fin.readline()
1339 1337 if not line:
1340 1338 raise EOFError
1341 1339 line = line.rstrip(pycompat.oslinesep)
1342 1340
1343 1341 return line
1344 1342
1345 1343 def prompt(self, msg, default="y"):
1346 1344 """Prompt user with msg, read response.
1347 1345 If ui is not interactive, the default is returned.
1348 1346 """
1349 1347 if not self.interactive():
1350 1348 self.write(msg, ' ', default or '', "\n")
1351 1349 return default
1352 1350 self._writenobuf(self.fout, msg, label='ui.prompt')
1353 1351 self.flush()
1354 1352 try:
1355 1353 r = self._readline()
1356 1354 if not r:
1357 1355 r = default
1358 1356 if self.configbool('ui', 'promptecho'):
1359 1357 self.write(r, "\n")
1360 1358 return r
1361 1359 except EOFError:
1362 1360 raise error.ResponseExpected()
1363 1361
1364 1362 @staticmethod
1365 1363 def extractchoices(prompt):
1366 1364 """Extract prompt message and list of choices from specified prompt.
1367 1365
1368 1366 This returns tuple "(message, choices)", and "choices" is the
1369 1367 list of tuple "(response character, text without &)".
1370 1368
1371 1369 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1372 1370 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1373 1371 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1374 1372 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1375 1373 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1376 1374 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1377 1375 """
1378 1376
1379 1377 # Sadly, the prompt string may have been built with a filename
1380 1378 # containing "$$" so let's try to find the first valid-looking
1381 1379 # prompt to start parsing. Sadly, we also can't rely on
1382 1380 # choices containing spaces, ASCII, or basically anything
1383 1381 # except an ampersand followed by a character.
1384 1382 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1385 1383 msg = m.group(1)
1386 1384 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1387 1385 def choicetuple(s):
1388 1386 ampidx = s.index('&')
1389 1387 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1390 1388 return (msg, [choicetuple(s) for s in choices])
1391 1389
1392 1390 def promptchoice(self, prompt, default=0):
1393 1391 """Prompt user with a message, read response, and ensure it matches
1394 1392 one of the provided choices. The prompt is formatted as follows:
1395 1393
1396 1394 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1397 1395
1398 1396 The index of the choice is returned. Responses are case
1399 1397 insensitive. If ui is not interactive, the default is
1400 1398 returned.
1401 1399 """
1402 1400
1403 1401 msg, choices = self.extractchoices(prompt)
1404 1402 resps = [r for r, t in choices]
1405 1403 while True:
1406 1404 r = self.prompt(msg, resps[default])
1407 1405 if r.lower() in resps:
1408 1406 return resps.index(r.lower())
1409 1407 self.write(_("unrecognized response\n"))
1410 1408
1411 1409 def getpass(self, prompt=None, default=None):
1412 1410 if not self.interactive():
1413 1411 return default
1414 1412 try:
1415 1413 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1416 1414 # disable getpass() only if explicitly specified. it's still valid
1417 1415 # to interact with tty even if fin is not a tty.
1418 1416 with self.timeblockedsection('stdio'):
1419 1417 if self.configbool('ui', 'nontty'):
1420 1418 l = self.fin.readline()
1421 1419 if not l:
1422 1420 raise EOFError
1423 1421 return l.rstrip('\n')
1424 1422 else:
1425 1423 return getpass.getpass('')
1426 1424 except EOFError:
1427 1425 raise error.ResponseExpected()
1428 1426
1429 1427 def status(self, *msg, **opts):
1430 1428 '''write status message to output (if ui.quiet is False)
1431 1429
1432 1430 This adds an output label of "ui.status".
1433 1431 '''
1434 1432 if not self.quiet:
1435 1433 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1436 1434 self.write(*msg, **opts)
1437 1435
1438 1436 def warn(self, *msg, **opts):
1439 1437 '''write warning message to output (stderr)
1440 1438
1441 1439 This adds an output label of "ui.warning".
1442 1440 '''
1443 1441 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1444 1442 self.write_err(*msg, **opts)
1445 1443
1446 1444 def error(self, *msg, **opts):
1447 1445 '''write error message to output (stderr)
1448 1446
1449 1447 This adds an output label of "ui.error".
1450 1448 '''
1451 1449 opts[r'label'] = opts.get(r'label', '') + ' ui.error'
1452 1450 self.write_err(*msg, **opts)
1453 1451
1454 1452 def note(self, *msg, **opts):
1455 1453 '''write note to output (if ui.verbose is True)
1456 1454
1457 1455 This adds an output label of "ui.note".
1458 1456 '''
1459 1457 if self.verbose:
1460 1458 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1461 1459 self.write(*msg, **opts)
1462 1460
1463 1461 def debug(self, *msg, **opts):
1464 1462 '''write debug message to output (if ui.debugflag is True)
1465 1463
1466 1464 This adds an output label of "ui.debug".
1467 1465 '''
1468 1466 if self.debugflag:
1469 1467 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1470 1468 self.write(*msg, **opts)
1471 1469
1472 1470 def edit(self, text, user, extra=None, editform=None, pending=None,
1473 1471 repopath=None, action=None):
1474 1472 if action is None:
1475 1473 self.develwarn('action is None but will soon be a required '
1476 1474 'parameter to ui.edit()')
1477 1475 extra_defaults = {
1478 1476 'prefix': 'editor',
1479 1477 'suffix': '.txt',
1480 1478 }
1481 1479 if extra is not None:
1482 1480 if extra.get('suffix') is not None:
1483 1481 self.develwarn('extra.suffix is not None but will soon be '
1484 1482 'ignored by ui.edit()')
1485 1483 extra_defaults.update(extra)
1486 1484 extra = extra_defaults
1487 1485
1488 1486 if action == 'diff':
1489 1487 suffix = '.diff'
1490 1488 elif action:
1491 1489 suffix = '.%s.hg.txt' % action
1492 1490 else:
1493 1491 suffix = extra['suffix']
1494 1492
1495 1493 rdir = None
1496 1494 if self.configbool('experimental', 'editortmpinhg'):
1497 1495 rdir = repopath
1498 1496 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1499 1497 suffix=suffix,
1500 1498 dir=rdir)
1501 1499 try:
1502 1500 f = os.fdopen(fd, r'wb')
1503 1501 f.write(util.tonativeeol(text))
1504 1502 f.close()
1505 1503
1506 1504 environ = {'HGUSER': user}
1507 1505 if 'transplant_source' in extra:
1508 1506 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1509 1507 for label in ('intermediate-source', 'source', 'rebase_source'):
1510 1508 if label in extra:
1511 1509 environ.update({'HGREVISION': extra[label]})
1512 1510 break
1513 1511 if editform:
1514 1512 environ.update({'HGEDITFORM': editform})
1515 1513 if pending:
1516 1514 environ.update({'HG_PENDING': pending})
1517 1515
1518 1516 editor = self.geteditor()
1519 1517
1520 1518 self.system("%s \"%s\"" % (editor, name),
1521 1519 environ=environ,
1522 1520 onerr=error.Abort, errprefix=_("edit failed"),
1523 1521 blockedtag='editor')
1524 1522
1525 1523 f = open(name, r'rb')
1526 1524 t = util.fromnativeeol(f.read())
1527 1525 f.close()
1528 1526 finally:
1529 1527 os.unlink(name)
1530 1528
1531 1529 return t
1532 1530
1533 1531 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1534 1532 blockedtag=None):
1535 1533 '''execute shell command with appropriate output stream. command
1536 1534 output will be redirected if fout is not stdout.
1537 1535
1538 1536 if command fails and onerr is None, return status, else raise onerr
1539 1537 object as exception.
1540 1538 '''
1541 1539 if blockedtag is None:
1542 1540 # Long cmds tend to be because of an absolute path on cmd. Keep
1543 1541 # the tail end instead
1544 1542 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1545 1543 blockedtag = 'unknown_system_' + cmdsuffix
1546 1544 out = self.fout
1547 1545 if any(s[1] for s in self._bufferstates):
1548 1546 out = self
1549 1547 with self.timeblockedsection(blockedtag):
1550 1548 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1551 1549 if rc and onerr:
1552 1550 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1553 1551 procutil.explainexit(rc))
1554 1552 if errprefix:
1555 1553 errmsg = '%s: %s' % (errprefix, errmsg)
1556 1554 raise onerr(errmsg)
1557 1555 return rc
1558 1556
1559 1557 def _runsystem(self, cmd, environ, cwd, out):
1560 1558 """actually execute the given shell command (can be overridden by
1561 1559 extensions like chg)"""
1562 1560 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1563 1561
1564 1562 def traceback(self, exc=None, force=False):
1565 1563 '''print exception traceback if traceback printing enabled or forced.
1566 1564 only to call in exception handler. returns true if traceback
1567 1565 printed.'''
1568 1566 if self.tracebackflag or force:
1569 1567 if exc is None:
1570 1568 exc = sys.exc_info()
1571 1569 cause = getattr(exc[1], 'cause', None)
1572 1570
1573 1571 if cause is not None:
1574 1572 causetb = traceback.format_tb(cause[2])
1575 1573 exctb = traceback.format_tb(exc[2])
1576 1574 exconly = traceback.format_exception_only(cause[0], cause[1])
1577 1575
1578 1576 # exclude frame where 'exc' was chained and rethrown from exctb
1579 1577 self.write_err('Traceback (most recent call last):\n',
1580 1578 ''.join(exctb[:-1]),
1581 1579 ''.join(causetb),
1582 1580 ''.join(exconly))
1583 1581 else:
1584 1582 output = traceback.format_exception(exc[0], exc[1], exc[2])
1585 1583 self.write_err(encoding.strtolocal(r''.join(output)))
1586 1584 return self.tracebackflag or force
1587 1585
1588 1586 def geteditor(self):
1589 1587 '''return editor to use'''
1590 1588 if pycompat.sysplatform == 'plan9':
1591 1589 # vi is the MIPS instruction simulator on Plan 9. We
1592 1590 # instead default to E to plumb commit messages to
1593 1591 # avoid confusion.
1594 1592 editor = 'E'
1595 1593 else:
1596 1594 editor = 'vi'
1597 1595 return (encoding.environ.get("HGEDITOR") or
1598 1596 self.config("ui", "editor", editor))
1599 1597
1600 1598 @util.propertycache
1601 1599 def _progbar(self):
1602 1600 """setup the progbar singleton to the ui object"""
1603 1601 if (self.quiet or self.debugflag
1604 1602 or self.configbool('progress', 'disable')
1605 1603 or not progress.shouldprint(self)):
1606 1604 return None
1607 1605 return getprogbar(self)
1608 1606
1609 1607 def _progclear(self):
1610 1608 """clear progress bar output if any. use it before any output"""
1611 1609 if not haveprogbar(): # nothing loaded yet
1612 1610 return
1613 1611 if self._progbar is not None and self._progbar.printed:
1614 1612 self._progbar.clear()
1615 1613
1616 1614 def progress(self, topic, pos, item="", unit="", total=None):
1617 1615 '''show a progress message
1618 1616
1619 1617 By default a textual progress bar will be displayed if an operation
1620 1618 takes too long. 'topic' is the current operation, 'item' is a
1621 1619 non-numeric marker of the current position (i.e. the currently
1622 1620 in-process file), 'pos' is the current numeric position (i.e.
1623 1621 revision, bytes, etc.), unit is a corresponding unit label,
1624 1622 and total is the highest expected pos.
1625 1623
1626 1624 Multiple nested topics may be active at a time.
1627 1625
1628 1626 All topics should be marked closed by setting pos to None at
1629 1627 termination.
1630 1628 '''
1631 1629 if self._progbar is not None:
1632 1630 self._progbar.progress(topic, pos, item=item, unit=unit,
1633 1631 total=total)
1634 1632 if pos is None or not self.configbool('progress', 'debug'):
1635 1633 return
1636 1634
1637 1635 if unit:
1638 1636 unit = ' ' + unit
1639 1637 if item:
1640 1638 item = ' ' + item
1641 1639
1642 1640 if total:
1643 1641 pct = 100.0 * pos / total
1644 1642 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1645 1643 % (topic, item, pos, total, unit, pct))
1646 1644 else:
1647 1645 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1648 1646
1649 1647 def makeprogress(self, topic, unit="", total=None):
1650 1648 '''exists only so low-level modules won't need to import scmutil'''
1651 1649 return scmutil.progress(self, topic, unit, total)
1652 1650
1653 1651 def log(self, service, *msg, **opts):
1654 1652 '''hook for logging facility extensions
1655 1653
1656 1654 service should be a readily-identifiable subsystem, which will
1657 1655 allow filtering.
1658 1656
1659 1657 *msg should be a newline-terminated format string to log, and
1660 1658 then any values to %-format into that format string.
1661 1659
1662 1660 **opts currently has no defined meanings.
1663 1661 '''
1664 1662
1665 1663 def label(self, msg, label):
1666 1664 '''style msg based on supplied label
1667 1665
1668 1666 If some color mode is enabled, this will add the necessary control
1669 1667 characters to apply such color. In addition, 'debug' color mode adds
1670 1668 markup showing which label affects a piece of text.
1671 1669
1672 1670 ui.write(s, 'label') is equivalent to
1673 1671 ui.write(ui.label(s, 'label')).
1674 1672 '''
1675 1673 if self._colormode is not None:
1676 1674 return color.colorlabel(self, msg, label)
1677 1675 return msg
1678 1676
1679 1677 def develwarn(self, msg, stacklevel=1, config=None):
1680 1678 """issue a developer warning message
1681 1679
1682 1680 Use 'stacklevel' to report the offender some layers further up in the
1683 1681 stack.
1684 1682 """
1685 1683 if not self.configbool('devel', 'all-warnings'):
1686 1684 if config is None or not self.configbool('devel', config):
1687 1685 return
1688 1686 msg = 'devel-warn: ' + msg
1689 1687 stacklevel += 1 # get in develwarn
1690 1688 if self.tracebackflag:
1691 1689 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1692 1690 self.log('develwarn', '%s at:\n%s' %
1693 1691 (msg, ''.join(util.getstackframes(stacklevel))))
1694 1692 else:
1695 1693 curframe = inspect.currentframe()
1696 1694 calframe = inspect.getouterframes(curframe, 2)
1697 1695 fname, lineno, fmsg = calframe[stacklevel][1:4]
1698 1696 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1699 1697 self.write_err('%s at: %s:%d (%s)\n'
1700 1698 % (msg, fname, lineno, fmsg))
1701 1699 self.log('develwarn', '%s at: %s:%d (%s)\n',
1702 1700 msg, fname, lineno, fmsg)
1703 1701 curframe = calframe = None # avoid cycles
1704 1702
1705 1703 def deprecwarn(self, msg, version, stacklevel=2):
1706 1704 """issue a deprecation warning
1707 1705
1708 1706 - msg: message explaining what is deprecated and how to upgrade,
1709 1707 - version: last version where the API will be supported,
1710 1708 """
1711 1709 if not (self.configbool('devel', 'all-warnings')
1712 1710 or self.configbool('devel', 'deprec-warn')):
1713 1711 return
1714 1712 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1715 1713 " update your code.)") % version
1716 1714 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1717 1715
1718 1716 def exportableenviron(self):
1719 1717 """The environment variables that are safe to export, e.g. through
1720 1718 hgweb.
1721 1719 """
1722 1720 return self._exportableenviron
1723 1721
1724 1722 @contextlib.contextmanager
1725 1723 def configoverride(self, overrides, source=""):
1726 1724 """Context manager for temporary config overrides
1727 1725 `overrides` must be a dict of the following structure:
1728 1726 {(section, name) : value}"""
1729 1727 backups = {}
1730 1728 try:
1731 1729 for (section, name), value in overrides.items():
1732 1730 backups[(section, name)] = self.backupconfig(section, name)
1733 1731 self.setconfig(section, name, value, source)
1734 1732 yield
1735 1733 finally:
1736 1734 for __, backup in backups.items():
1737 1735 self.restoreconfig(backup)
1738 1736 # just restoring ui.quiet config to the previous value is not enough
1739 1737 # as it does not update ui.quiet class member
1740 1738 if ('ui', 'quiet') in overrides:
1741 1739 self.fixconfig(section='ui')
1742 1740
1743 1741 class paths(dict):
1744 1742 """Represents a collection of paths and their configs.
1745 1743
1746 1744 Data is initially derived from ui instances and the config files they have
1747 1745 loaded.
1748 1746 """
1749 1747 def __init__(self, ui):
1750 1748 dict.__init__(self)
1751 1749
1752 1750 for name, loc in ui.configitems('paths', ignoresub=True):
1753 1751 # No location is the same as not existing.
1754 1752 if not loc:
1755 1753 continue
1756 1754 loc, sub = ui.configsuboptions('paths', name)
1757 1755 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1758 1756
1759 1757 def getpath(self, name, default=None):
1760 1758 """Return a ``path`` from a string, falling back to default.
1761 1759
1762 1760 ``name`` can be a named path or locations. Locations are filesystem
1763 1761 paths or URIs.
1764 1762
1765 1763 Returns None if ``name`` is not a registered path, a URI, or a local
1766 1764 path to a repo.
1767 1765 """
1768 1766 # Only fall back to default if no path was requested.
1769 1767 if name is None:
1770 1768 if not default:
1771 1769 default = ()
1772 1770 elif not isinstance(default, (tuple, list)):
1773 1771 default = (default,)
1774 1772 for k in default:
1775 1773 try:
1776 1774 return self[k]
1777 1775 except KeyError:
1778 1776 continue
1779 1777 return None
1780 1778
1781 1779 # Most likely empty string.
1782 1780 # This may need to raise in the future.
1783 1781 if not name:
1784 1782 return None
1785 1783
1786 1784 try:
1787 1785 return self[name]
1788 1786 except KeyError:
1789 1787 # Try to resolve as a local path or URI.
1790 1788 try:
1791 1789 # We don't pass sub-options in, so no need to pass ui instance.
1792 1790 return path(None, None, rawloc=name)
1793 1791 except ValueError:
1794 1792 raise error.RepoError(_('repository %s does not exist') %
1795 1793 name)
1796 1794
1797 1795 _pathsuboptions = {}
1798 1796
1799 1797 def pathsuboption(option, attr):
1800 1798 """Decorator used to declare a path sub-option.
1801 1799
1802 1800 Arguments are the sub-option name and the attribute it should set on
1803 1801 ``path`` instances.
1804 1802
1805 1803 The decorated function will receive as arguments a ``ui`` instance,
1806 1804 ``path`` instance, and the string value of this option from the config.
1807 1805 The function should return the value that will be set on the ``path``
1808 1806 instance.
1809 1807
1810 1808 This decorator can be used to perform additional verification of
1811 1809 sub-options and to change the type of sub-options.
1812 1810 """
1813 1811 def register(func):
1814 1812 _pathsuboptions[option] = (attr, func)
1815 1813 return func
1816 1814 return register
1817 1815
1818 1816 @pathsuboption('pushurl', 'pushloc')
1819 1817 def pushurlpathoption(ui, path, value):
1820 1818 u = util.url(value)
1821 1819 # Actually require a URL.
1822 1820 if not u.scheme:
1823 1821 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1824 1822 return None
1825 1823
1826 1824 # Don't support the #foo syntax in the push URL to declare branch to
1827 1825 # push.
1828 1826 if u.fragment:
1829 1827 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1830 1828 'ignoring)\n') % path.name)
1831 1829 u.fragment = None
1832 1830
1833 1831 return bytes(u)
1834 1832
1835 1833 @pathsuboption('pushrev', 'pushrev')
1836 1834 def pushrevpathoption(ui, path, value):
1837 1835 return value
1838 1836
1839 1837 class path(object):
1840 1838 """Represents an individual path and its configuration."""
1841 1839
1842 1840 def __init__(self, ui, name, rawloc=None, suboptions=None):
1843 1841 """Construct a path from its config options.
1844 1842
1845 1843 ``ui`` is the ``ui`` instance the path is coming from.
1846 1844 ``name`` is the symbolic name of the path.
1847 1845 ``rawloc`` is the raw location, as defined in the config.
1848 1846 ``pushloc`` is the raw locations pushes should be made to.
1849 1847
1850 1848 If ``name`` is not defined, we require that the location be a) a local
1851 1849 filesystem path with a .hg directory or b) a URL. If not,
1852 1850 ``ValueError`` is raised.
1853 1851 """
1854 1852 if not rawloc:
1855 1853 raise ValueError('rawloc must be defined')
1856 1854
1857 1855 # Locations may define branches via syntax <base>#<branch>.
1858 1856 u = util.url(rawloc)
1859 1857 branch = None
1860 1858 if u.fragment:
1861 1859 branch = u.fragment
1862 1860 u.fragment = None
1863 1861
1864 1862 self.url = u
1865 1863 self.branch = branch
1866 1864
1867 1865 self.name = name
1868 1866 self.rawloc = rawloc
1869 1867 self.loc = '%s' % u
1870 1868
1871 1869 # When given a raw location but not a symbolic name, validate the
1872 1870 # location is valid.
1873 1871 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1874 1872 raise ValueError('location is not a URL or path to a local '
1875 1873 'repo: %s' % rawloc)
1876 1874
1877 1875 suboptions = suboptions or {}
1878 1876
1879 1877 # Now process the sub-options. If a sub-option is registered, its
1880 1878 # attribute will always be present. The value will be None if there
1881 1879 # was no valid sub-option.
1882 1880 for suboption, (attr, func) in _pathsuboptions.iteritems():
1883 1881 if suboption not in suboptions:
1884 1882 setattr(self, attr, None)
1885 1883 continue
1886 1884
1887 1885 value = func(ui, self, suboptions[suboption])
1888 1886 setattr(self, attr, value)
1889 1887
1890 1888 def _isvalidlocalpath(self, path):
1891 1889 """Returns True if the given path is a potentially valid repository.
1892 1890 This is its own function so that extensions can change the definition of
1893 1891 'valid' in this case (like when pulling from a git repo into a hg
1894 1892 one)."""
1895 1893 return os.path.isdir(os.path.join(path, '.hg'))
1896 1894
1897 1895 @property
1898 1896 def suboptions(self):
1899 1897 """Return sub-options and their values for this path.
1900 1898
1901 1899 This is intended to be used for presentation purposes.
1902 1900 """
1903 1901 d = {}
1904 1902 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1905 1903 value = getattr(self, attr)
1906 1904 if value is not None:
1907 1905 d[subopt] = value
1908 1906 return d
1909 1907
1910 1908 # we instantiate one globally shared progress bar to avoid
1911 1909 # competing progress bars when multiple UI objects get created
1912 1910 _progresssingleton = None
1913 1911
1914 1912 def getprogbar(ui):
1915 1913 global _progresssingleton
1916 1914 if _progresssingleton is None:
1917 1915 # passing 'ui' object to the singleton is fishy,
1918 1916 # this is how the extension used to work but feel free to rework it.
1919 1917 _progresssingleton = progress.progbar(ui)
1920 1918 return _progresssingleton
1921 1919
1922 1920 def haveprogbar():
1923 1921 return _progresssingleton is not None
General Comments 0
You need to be logged in to leave comments. Login now