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