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