##// END OF EJS Templates
config: use level to properly deal with value priority...
marmoute -
r47368:b91a695b default
parent child Browse files
Show More
@@ -1,2398 +1,2405 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 datetime
13 13 import errno
14 14 import getpass
15 15 import inspect
16 16 import os
17 17 import re
18 18 import signal
19 19 import socket
20 20 import subprocess
21 21 import sys
22 22 import traceback
23 23
24 24 from .i18n import _
25 25 from .node import hex
26 26 from .pycompat import (
27 27 getattr,
28 28 open,
29 29 setattr,
30 30 )
31 31
32 32 from . import (
33 33 color,
34 34 config,
35 35 configitems,
36 36 encoding,
37 37 error,
38 38 formatter,
39 39 loggingutil,
40 40 progress,
41 41 pycompat,
42 42 rcutil,
43 43 scmutil,
44 44 util,
45 45 )
46 46 from .utils import (
47 47 dateutil,
48 48 procutil,
49 49 resourceutil,
50 50 stringutil,
51 51 )
52 52
53 53 urlreq = util.urlreq
54 54
55 55 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
56 56 _keepalnum = b''.join(
57 57 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
58 58 )
59 59
60 60 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
61 61 tweakrc = b"""
62 62 [ui]
63 63 # The rollback command is dangerous. As a rule, don't use it.
64 64 rollback = False
65 65 # Make `hg status` report copy information
66 66 statuscopies = yes
67 67 # Prefer curses UIs when available. Revert to plain-text with `text`.
68 68 interface = curses
69 69 # Make compatible commands emit cwd-relative paths by default.
70 70 relative-paths = yes
71 71
72 72 [commands]
73 73 # Grep working directory by default.
74 74 grep.all-files = True
75 75 # Refuse to perform an `hg update` that would cause a file content merge
76 76 update.check = noconflict
77 77 # Show conflicts information in `hg status`
78 78 status.verbose = True
79 79 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
80 80 resolve.explicit-re-merge = True
81 81
82 82 [diff]
83 83 git = 1
84 84 showfunc = 1
85 85 word-diff = 1
86 86 """
87 87
88 88 samplehgrcs = {
89 89 b'user': b"""# example user config (see 'hg help config' for more info)
90 90 [ui]
91 91 # name and email, e.g.
92 92 # username = Jane Doe <jdoe@example.com>
93 93 username =
94 94
95 95 # We recommend enabling tweakdefaults to get slight improvements to
96 96 # the UI over time. Make sure to set HGPLAIN in the environment when
97 97 # writing scripts!
98 98 # tweakdefaults = True
99 99
100 100 # uncomment to disable color in command output
101 101 # (see 'hg help color' for details)
102 102 # color = never
103 103
104 104 # uncomment to disable command output pagination
105 105 # (see 'hg help pager' for details)
106 106 # paginate = never
107 107
108 108 [extensions]
109 109 # uncomment the lines below to enable some popular extensions
110 110 # (see 'hg help extensions' for more info)
111 111 #
112 112 # histedit =
113 113 # rebase =
114 114 # uncommit =
115 115 """,
116 116 b'cloned': b"""# example repository config (see 'hg help config' for more info)
117 117 [paths]
118 118 default = %s
119 119
120 120 # path aliases to other clones of this repo in URLs or filesystem paths
121 121 # (see 'hg help config.paths' for more info)
122 122 #
123 123 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
124 124 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
125 125 # my-clone = /home/jdoe/jdoes-clone
126 126
127 127 [ui]
128 128 # name and email (local to this repository, optional), e.g.
129 129 # username = Jane Doe <jdoe@example.com>
130 130 """,
131 131 b'local': b"""# example repository config (see 'hg help config' for more info)
132 132 [paths]
133 133 # path aliases to other clones of this repo in URLs or filesystem paths
134 134 # (see 'hg help config.paths' for more info)
135 135 #
136 136 # default = http://example.com/hg/example-repo
137 137 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
138 138 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
139 139 # my-clone = /home/jdoe/jdoes-clone
140 140
141 141 [ui]
142 142 # name and email (local to this repository, optional), e.g.
143 143 # username = Jane Doe <jdoe@example.com>
144 144 """,
145 145 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
146 146
147 147 [ui]
148 148 # uncomment to disable color in command output
149 149 # (see 'hg help color' for details)
150 150 # color = never
151 151
152 152 # uncomment to disable command output pagination
153 153 # (see 'hg help pager' for details)
154 154 # paginate = never
155 155
156 156 [extensions]
157 157 # uncomment the lines below to enable some popular extensions
158 158 # (see 'hg help extensions' for more info)
159 159 #
160 160 # blackbox =
161 161 # churn =
162 162 """,
163 163 }
164 164
165 165
166 166 def _maybestrurl(maybebytes):
167 167 return pycompat.rapply(pycompat.strurl, maybebytes)
168 168
169 169
170 170 def _maybebytesurl(maybestr):
171 171 return pycompat.rapply(pycompat.bytesurl, maybestr)
172 172
173 173
174 174 class httppasswordmgrdbproxy(object):
175 175 """Delays loading urllib2 until it's needed."""
176 176
177 177 def __init__(self):
178 178 self._mgr = None
179 179
180 180 def _get_mgr(self):
181 181 if self._mgr is None:
182 182 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
183 183 return self._mgr
184 184
185 185 def add_password(self, realm, uris, user, passwd):
186 186 return self._get_mgr().add_password(
187 187 _maybestrurl(realm),
188 188 _maybestrurl(uris),
189 189 _maybestrurl(user),
190 190 _maybestrurl(passwd),
191 191 )
192 192
193 193 def find_user_password(self, realm, uri):
194 194 mgr = self._get_mgr()
195 195 return _maybebytesurl(
196 196 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
197 197 )
198 198
199 199
200 200 def _catchterm(*args):
201 201 raise error.SignalInterrupt
202 202
203 203
204 204 # unique object used to detect no default value has been provided when
205 205 # retrieving configuration value.
206 206 _unset = object()
207 207
208 208 # _reqexithandlers: callbacks run at the end of a request
209 209 _reqexithandlers = []
210 210
211 211
212 212 class ui(object):
213 213 def __init__(self, src=None):
214 214 """Create a fresh new ui object if no src given
215 215
216 216 Use uimod.ui.load() to create a ui which knows global and user configs.
217 217 In most cases, you should use ui.copy() to create a copy of an existing
218 218 ui object.
219 219 """
220 220 # _buffers: used for temporary capture of output
221 221 self._buffers = []
222 222 # 3-tuple describing how each buffer in the stack behaves.
223 223 # Values are (capture stderr, capture subprocesses, apply labels).
224 224 self._bufferstates = []
225 225 # When a buffer is active, defines whether we are expanding labels.
226 226 # This exists to prevent an extra list lookup.
227 227 self._bufferapplylabels = None
228 228 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
229 229 self._reportuntrusted = True
230 230 self._knownconfig = configitems.coreitems
231 231 self._ocfg = config.config() # overlay
232 232 self._tcfg = config.config() # trusted
233 233 self._ucfg = config.config() # untrusted
234 234 self._trustusers = set()
235 235 self._trustgroups = set()
236 236 self.callhooks = True
237 237 # Insecure server connections requested.
238 238 self.insecureconnections = False
239 239 # Blocked time
240 240 self.logblockedtimes = False
241 241 # color mode: see mercurial/color.py for possible value
242 242 self._colormode = None
243 243 self._terminfoparams = {}
244 244 self._styles = {}
245 245 self._uninterruptible = False
246 246 self.showtimestamp = False
247 247
248 248 if src:
249 249 self._fout = src._fout
250 250 self._ferr = src._ferr
251 251 self._fin = src._fin
252 252 self._fmsg = src._fmsg
253 253 self._fmsgout = src._fmsgout
254 254 self._fmsgerr = src._fmsgerr
255 255 self._finoutredirected = src._finoutredirected
256 256 self._loggers = src._loggers.copy()
257 257 self.pageractive = src.pageractive
258 258 self._disablepager = src._disablepager
259 259 self._tweaked = src._tweaked
260 260
261 261 self._tcfg = src._tcfg.copy()
262 262 self._ucfg = src._ucfg.copy()
263 263 self._ocfg = src._ocfg.copy()
264 264 self._trustusers = src._trustusers.copy()
265 265 self._trustgroups = src._trustgroups.copy()
266 266 self.environ = src.environ
267 267 self.callhooks = src.callhooks
268 268 self.insecureconnections = src.insecureconnections
269 269 self._colormode = src._colormode
270 270 self._terminfoparams = src._terminfoparams.copy()
271 271 self._styles = src._styles.copy()
272 272
273 273 self.fixconfig()
274 274
275 275 self.httppasswordmgrdb = src.httppasswordmgrdb
276 276 self._blockedtimes = src._blockedtimes
277 277 else:
278 278 self._fout = procutil.stdout
279 279 self._ferr = procutil.stderr
280 280 self._fin = procutil.stdin
281 281 self._fmsg = None
282 282 self._fmsgout = self.fout # configurable
283 283 self._fmsgerr = self.ferr # configurable
284 284 self._finoutredirected = False
285 285 self._loggers = {}
286 286 self.pageractive = False
287 287 self._disablepager = False
288 288 self._tweaked = False
289 289
290 290 # shared read-only environment
291 291 self.environ = encoding.environ
292 292
293 293 self.httppasswordmgrdb = httppasswordmgrdbproxy()
294 294 self._blockedtimes = collections.defaultdict(int)
295 295
296 296 allowed = self.configlist(b'experimental', b'exportableenviron')
297 297 if b'*' in allowed:
298 298 self._exportableenviron = self.environ
299 299 else:
300 300 self._exportableenviron = {}
301 301 for k in allowed:
302 302 if k in self.environ:
303 303 self._exportableenviron[k] = self.environ[k]
304 304
305 305 def _new_source(self):
306 306 self._ocfg.new_source()
307 307 self._tcfg.new_source()
308 308 self._ucfg.new_source()
309 309
310 310 @classmethod
311 311 def load(cls):
312 312 """Create a ui and load global and user configs"""
313 313 u = cls()
314 314 # we always trust global config files and environment variables
315 315 for t, f in rcutil.rccomponents():
316 316 if t == b'path':
317 317 u.readconfig(f, trust=True)
318 318 elif t == b'resource':
319 319 u.read_resource_config(f, trust=True)
320 320 elif t == b'items':
321 321 u._new_source()
322 322 sections = set()
323 323 for section, name, value, source in f:
324 324 # do not set u._ocfg
325 325 # XXX clean this up once immutable config object is a thing
326 326 u._tcfg.set(section, name, value, source)
327 327 u._ucfg.set(section, name, value, source)
328 328 sections.add(section)
329 329 for section in sections:
330 330 u.fixconfig(section=section)
331 331 else:
332 332 raise error.ProgrammingError(b'unknown rctype: %s' % t)
333 333 u._maybetweakdefaults()
334 334 u._new_source() # anything after that is a different level
335 335 return u
336 336
337 337 def _maybetweakdefaults(self):
338 338 if not self.configbool(b'ui', b'tweakdefaults'):
339 339 return
340 340 if self._tweaked or self.plain(b'tweakdefaults'):
341 341 return
342 342
343 343 # Note: it is SUPER IMPORTANT that you set self._tweaked to
344 344 # True *before* any calls to setconfig(), otherwise you'll get
345 345 # infinite recursion between setconfig and this method.
346 346 #
347 347 # TODO: We should extract an inner method in setconfig() to
348 348 # avoid this weirdness.
349 349 self._tweaked = True
350 350 tmpcfg = config.config()
351 351 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
352 352 for section in tmpcfg:
353 353 for name, value in tmpcfg.items(section):
354 354 if not self.hasconfig(section, name):
355 355 self.setconfig(section, name, value, b"<tweakdefaults>")
356 356
357 357 def copy(self):
358 358 return self.__class__(self)
359 359
360 360 def resetstate(self):
361 361 """Clear internal state that shouldn't persist across commands"""
362 362 if self._progbar:
363 363 self._progbar.resetstate() # reset last-print time of progress bar
364 364 self.httppasswordmgrdb = httppasswordmgrdbproxy()
365 365
366 366 @contextlib.contextmanager
367 367 def timeblockedsection(self, key):
368 368 # this is open-coded below - search for timeblockedsection to find them
369 369 starttime = util.timer()
370 370 try:
371 371 yield
372 372 finally:
373 373 self._blockedtimes[key + b'_blocked'] += (
374 374 util.timer() - starttime
375 375 ) * 1000
376 376
377 377 @contextlib.contextmanager
378 378 def uninterruptible(self):
379 379 """Mark an operation as unsafe.
380 380
381 381 Most operations on a repository are safe to interrupt, but a
382 382 few are risky (for example repair.strip). This context manager
383 383 lets you advise Mercurial that something risky is happening so
384 384 that control-C etc can be blocked if desired.
385 385 """
386 386 enabled = self.configbool(b'experimental', b'nointerrupt')
387 387 if enabled and self.configbool(
388 388 b'experimental', b'nointerrupt-interactiveonly'
389 389 ):
390 390 enabled = self.interactive()
391 391 if self._uninterruptible or not enabled:
392 392 # if nointerrupt support is turned off, the process isn't
393 393 # interactive, or we're already in an uninterruptible
394 394 # block, do nothing.
395 395 yield
396 396 return
397 397
398 398 def warn():
399 399 self.warn(_(b"shutting down cleanly\n"))
400 400 self.warn(
401 401 _(b"press ^C again to terminate immediately (dangerous)\n")
402 402 )
403 403 return True
404 404
405 405 with procutil.uninterruptible(warn):
406 406 try:
407 407 self._uninterruptible = True
408 408 yield
409 409 finally:
410 410 self._uninterruptible = False
411 411
412 412 def formatter(self, topic, opts):
413 413 return formatter.formatter(self, self, topic, opts)
414 414
415 415 def _trusted(self, fp, f):
416 416 st = util.fstat(fp)
417 417 if util.isowner(st):
418 418 return True
419 419
420 420 tusers, tgroups = self._trustusers, self._trustgroups
421 421 if b'*' in tusers or b'*' in tgroups:
422 422 return True
423 423
424 424 user = util.username(st.st_uid)
425 425 group = util.groupname(st.st_gid)
426 426 if user in tusers or group in tgroups or user == util.username():
427 427 return True
428 428
429 429 if self._reportuntrusted:
430 430 self.warn(
431 431 _(
432 432 b'not trusting file %s from untrusted '
433 433 b'user %s, group %s\n'
434 434 )
435 435 % (f, user, group)
436 436 )
437 437 return False
438 438
439 439 def read_resource_config(
440 440 self, name, root=None, trust=False, sections=None, remap=None
441 441 ):
442 442 try:
443 443 fp = resourceutil.open_resource(name[0], name[1])
444 444 except IOError:
445 445 if not sections: # ignore unless we were looking for something
446 446 return
447 447 raise
448 448
449 449 self._readconfig(
450 450 b'resource:%s.%s' % name, fp, root, trust, sections, remap
451 451 )
452 452
453 453 def readconfig(
454 454 self, filename, root=None, trust=False, sections=None, remap=None
455 455 ):
456 456 try:
457 457 fp = open(filename, 'rb')
458 458 except IOError:
459 459 if not sections: # ignore unless we were looking for something
460 460 return
461 461 raise
462 462
463 463 self._readconfig(filename, fp, root, trust, sections, remap)
464 464
465 465 def _readconfig(
466 466 self, filename, fp, root=None, trust=False, sections=None, remap=None
467 467 ):
468 468 with fp:
469 469 cfg = config.config()
470 470 trusted = sections or trust or self._trusted(fp, filename)
471 471
472 472 try:
473 473 cfg.read(filename, fp, sections=sections, remap=remap)
474 474 except error.ConfigError as inst:
475 475 if trusted:
476 476 raise
477 477 self.warn(
478 478 _(b'ignored %s: %s\n') % (inst.location, inst.message)
479 479 )
480 480
481 481 self._applyconfig(cfg, trusted, root)
482 482
483 483 def applyconfig(self, configitems, source=b"", root=None):
484 484 """Add configitems from a non-file source. Unlike with ``setconfig()``,
485 485 they can be overridden by subsequent config file reads. The items are
486 486 in the same format as ``configoverride()``, namely a dict of the
487 487 following structures: {(section, name) : value}
488 488
489 489 Typically this is used by extensions that inject themselves into the
490 490 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
491 491 """
492 492 cfg = config.config()
493 493
494 494 for (section, name), value in configitems.items():
495 495 cfg.set(section, name, value, source)
496 496
497 497 self._applyconfig(cfg, True, root)
498 498
499 499 def _applyconfig(self, cfg, trusted, root):
500 500 if self.plain():
501 501 for k in (
502 502 b'debug',
503 503 b'fallbackencoding',
504 504 b'quiet',
505 505 b'slash',
506 506 b'logtemplate',
507 507 b'message-output',
508 508 b'statuscopies',
509 509 b'style',
510 510 b'traceback',
511 511 b'verbose',
512 512 ):
513 513 if k in cfg[b'ui']:
514 514 del cfg[b'ui'][k]
515 515 for k, v in cfg.items(b'defaults'):
516 516 del cfg[b'defaults'][k]
517 517 for k, v in cfg.items(b'commands'):
518 518 del cfg[b'commands'][k]
519 519 for k, v in cfg.items(b'command-templates'):
520 520 del cfg[b'command-templates'][k]
521 521 # Don't remove aliases from the configuration if in the exceptionlist
522 522 if self.plain(b'alias'):
523 523 for k, v in cfg.items(b'alias'):
524 524 del cfg[b'alias'][k]
525 525 if self.plain(b'revsetalias'):
526 526 for k, v in cfg.items(b'revsetalias'):
527 527 del cfg[b'revsetalias'][k]
528 528 if self.plain(b'templatealias'):
529 529 for k, v in cfg.items(b'templatealias'):
530 530 del cfg[b'templatealias'][k]
531 531
532 532 if trusted:
533 533 self._tcfg.update(cfg)
534 534 self._tcfg.update(self._ocfg)
535 535 self._ucfg.update(cfg)
536 536 self._ucfg.update(self._ocfg)
537 537
538 538 if root is None:
539 539 root = os.path.expanduser(b'~')
540 540 self.fixconfig(root=root)
541 541
542 542 def fixconfig(self, root=None, section=None):
543 543 if section in (None, b'paths'):
544 544 # expand vars and ~
545 545 # translate paths relative to root (or home) into absolute paths
546 546 root = root or encoding.getcwd()
547 547 for c in self._tcfg, self._ucfg, self._ocfg:
548 548 for n, p in c.items(b'paths'):
549 549 # Ignore sub-options.
550 550 if b':' in n:
551 551 continue
552 552 if not p:
553 553 continue
554 554 if b'%%' in p:
555 555 s = self.configsource(b'paths', n) or b'none'
556 556 self.warn(
557 557 _(b"(deprecated '%%' in path %s=%s from %s)\n")
558 558 % (n, p, s)
559 559 )
560 560 p = p.replace(b'%%', b'%')
561 561 p = util.expandpath(p)
562 562 if not util.hasscheme(p) and not os.path.isabs(p):
563 563 p = os.path.normpath(os.path.join(root, p))
564 564 c.alter(b"paths", n, p)
565 565
566 566 if section in (None, b'ui'):
567 567 # update ui options
568 568 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
569 569 self.debugflag = self.configbool(b'ui', b'debug')
570 570 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
571 571 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
572 572 if self.verbose and self.quiet:
573 573 self.quiet = self.verbose = False
574 574 self._reportuntrusted = self.debugflag or self.configbool(
575 575 b"ui", b"report_untrusted"
576 576 )
577 577 self.showtimestamp = self.configbool(b'ui', b'timestamp-output')
578 578 self.tracebackflag = self.configbool(b'ui', b'traceback')
579 579 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
580 580
581 581 if section in (None, b'trusted'):
582 582 # update trust information
583 583 self._trustusers.update(self.configlist(b'trusted', b'users'))
584 584 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
585 585
586 586 if section in (None, b'devel', b'ui') and self.debugflag:
587 587 tracked = set()
588 588 if self.configbool(b'devel', b'debug.extensions'):
589 589 tracked.add(b'extension')
590 590 if tracked:
591 591 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
592 592 self.setlogger(b'debug', logger)
593 593
594 594 def backupconfig(self, section, item):
595 595 return (
596 596 self._ocfg.backup(section, item),
597 597 self._tcfg.backup(section, item),
598 598 self._ucfg.backup(section, item),
599 599 )
600 600
601 601 def restoreconfig(self, data):
602 602 self._ocfg.restore(data[0])
603 603 self._tcfg.restore(data[1])
604 604 self._ucfg.restore(data[2])
605 605
606 606 def setconfig(self, section, name, value, source=b''):
607 607 for cfg in (self._ocfg, self._tcfg, self._ucfg):
608 608 cfg.set(section, name, value, source)
609 609 self.fixconfig(section=section)
610 610 self._maybetweakdefaults()
611 611
612 612 def _data(self, untrusted):
613 613 return untrusted and self._ucfg or self._tcfg
614 614
615 615 def configsource(self, section, name, untrusted=False):
616 616 return self._data(untrusted).source(section, name)
617 617
618 618 def config(self, section, name, default=_unset, untrusted=False):
619 619 """return the plain string version of a config"""
620 620 value = self._config(
621 621 section, name, default=default, untrusted=untrusted
622 622 )
623 623 if value is _unset:
624 624 return None
625 625 return value
626 626
627 627 def _config(self, section, name, default=_unset, untrusted=False):
628 628 value = itemdefault = default
629 629 item = self._knownconfig.get(section, {}).get(name)
630 630 alternates = [(section, name)]
631 631
632 632 if item is not None:
633 633 alternates.extend(item.alias)
634 634 if callable(item.default):
635 635 itemdefault = item.default()
636 636 else:
637 637 itemdefault = item.default
638 638 else:
639 639 msg = b"accessing unregistered config item: '%s.%s'"
640 640 msg %= (section, name)
641 641 self.develwarn(msg, 2, b'warn-config-unknown')
642 642
643 643 if default is _unset:
644 644 if item is None:
645 645 value = default
646 646 elif item.default is configitems.dynamicdefault:
647 647 value = None
648 648 msg = b"config item requires an explicit default value: '%s.%s'"
649 649 msg %= (section, name)
650 650 self.develwarn(msg, 2, b'warn-config-default')
651 651 else:
652 652 value = itemdefault
653 653 elif (
654 654 item is not None
655 655 and item.default is not configitems.dynamicdefault
656 656 and default != itemdefault
657 657 ):
658 658 msg = (
659 659 b"specifying a mismatched default value for a registered "
660 660 b"config item: '%s.%s' '%s'"
661 661 )
662 662 msg %= (section, name, pycompat.bytestr(default))
663 663 self.develwarn(msg, 2, b'warn-config-default')
664 664
665 candidates = []
666 config = self._data(untrusted)
665 667 for s, n in alternates:
666 candidate = self._data(untrusted).get(s, n, None)
668 candidate = config.get(s, n, None)
667 669 if candidate is not None:
668 value = candidate
669 break
670 candidates.append((s, n, candidate))
671 if candidates:
672
673 def level(x):
674 return config.level(x[0], x[1])
675
676 value = max(candidates, key=level)[2]
670 677
671 678 if self.debugflag and not untrusted and self._reportuntrusted:
672 679 for s, n in alternates:
673 680 uvalue = self._ucfg.get(s, n)
674 681 if uvalue is not None and uvalue != value:
675 682 self.debug(
676 683 b"ignoring untrusted configuration option "
677 684 b"%s.%s = %s\n" % (s, n, uvalue)
678 685 )
679 686 return value
680 687
681 688 def config_default(self, section, name):
682 689 """return the default value for a config option
683 690
684 691 The default is returned "raw", for example if it is a callable, the
685 692 callable was not called.
686 693 """
687 694 item = self._knownconfig.get(section, {}).get(name)
688 695
689 696 if item is None:
690 697 raise KeyError((section, name))
691 698 return item.default
692 699
693 700 def configsuboptions(self, section, name, default=_unset, untrusted=False):
694 701 """Get a config option and all sub-options.
695 702
696 703 Some config options have sub-options that are declared with the
697 704 format "key:opt = value". This method is used to return the main
698 705 option and all its declared sub-options.
699 706
700 707 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
701 708 is a dict of defined sub-options where keys and values are strings.
702 709 """
703 710 main = self.config(section, name, default, untrusted=untrusted)
704 711 data = self._data(untrusted)
705 712 sub = {}
706 713 prefix = b'%s:' % name
707 714 for k, v in data.items(section):
708 715 if k.startswith(prefix):
709 716 sub[k[len(prefix) :]] = v
710 717
711 718 if self.debugflag and not untrusted and self._reportuntrusted:
712 719 for k, v in sub.items():
713 720 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
714 721 if uvalue is not None and uvalue != v:
715 722 self.debug(
716 723 b'ignoring untrusted configuration option '
717 724 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
718 725 )
719 726
720 727 return main, sub
721 728
722 729 def configpath(self, section, name, default=_unset, untrusted=False):
723 730 """get a path config item, expanded relative to repo root or config
724 731 file"""
725 732 v = self.config(section, name, default, untrusted)
726 733 if v is None:
727 734 return None
728 735 if not os.path.isabs(v) or b"://" not in v:
729 736 src = self.configsource(section, name, untrusted)
730 737 if b':' in src:
731 738 base = os.path.dirname(src.rsplit(b':')[0])
732 739 v = os.path.join(base, os.path.expanduser(v))
733 740 return v
734 741
735 742 def configbool(self, section, name, default=_unset, untrusted=False):
736 743 """parse a configuration element as a boolean
737 744
738 745 >>> u = ui(); s = b'foo'
739 746 >>> u.setconfig(s, b'true', b'yes')
740 747 >>> u.configbool(s, b'true')
741 748 True
742 749 >>> u.setconfig(s, b'false', b'no')
743 750 >>> u.configbool(s, b'false')
744 751 False
745 752 >>> u.configbool(s, b'unknown')
746 753 False
747 754 >>> u.configbool(s, b'unknown', True)
748 755 True
749 756 >>> u.setconfig(s, b'invalid', b'somevalue')
750 757 >>> u.configbool(s, b'invalid')
751 758 Traceback (most recent call last):
752 759 ...
753 760 ConfigError: foo.invalid is not a boolean ('somevalue')
754 761 """
755 762
756 763 v = self._config(section, name, default, untrusted=untrusted)
757 764 if v is None:
758 765 return v
759 766 if v is _unset:
760 767 if default is _unset:
761 768 return False
762 769 return default
763 770 if isinstance(v, bool):
764 771 return v
765 772 b = stringutil.parsebool(v)
766 773 if b is None:
767 774 raise error.ConfigError(
768 775 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
769 776 )
770 777 return b
771 778
772 779 def configwith(
773 780 self, convert, section, name, default=_unset, desc=None, untrusted=False
774 781 ):
775 782 """parse a configuration element with a conversion function
776 783
777 784 >>> u = ui(); s = b'foo'
778 785 >>> u.setconfig(s, b'float1', b'42')
779 786 >>> u.configwith(float, s, b'float1')
780 787 42.0
781 788 >>> u.setconfig(s, b'float2', b'-4.25')
782 789 >>> u.configwith(float, s, b'float2')
783 790 -4.25
784 791 >>> u.configwith(float, s, b'unknown', 7)
785 792 7.0
786 793 >>> u.setconfig(s, b'invalid', b'somevalue')
787 794 >>> u.configwith(float, s, b'invalid')
788 795 Traceback (most recent call last):
789 796 ...
790 797 ConfigError: foo.invalid is not a valid float ('somevalue')
791 798 >>> u.configwith(float, s, b'invalid', desc=b'womble')
792 799 Traceback (most recent call last):
793 800 ...
794 801 ConfigError: foo.invalid is not a valid womble ('somevalue')
795 802 """
796 803
797 804 v = self.config(section, name, default, untrusted)
798 805 if v is None:
799 806 return v # do not attempt to convert None
800 807 try:
801 808 return convert(v)
802 809 except (ValueError, error.ParseError):
803 810 if desc is None:
804 811 desc = pycompat.sysbytes(convert.__name__)
805 812 raise error.ConfigError(
806 813 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
807 814 )
808 815
809 816 def configint(self, section, name, default=_unset, untrusted=False):
810 817 """parse a configuration element as an integer
811 818
812 819 >>> u = ui(); s = b'foo'
813 820 >>> u.setconfig(s, b'int1', b'42')
814 821 >>> u.configint(s, b'int1')
815 822 42
816 823 >>> u.setconfig(s, b'int2', b'-42')
817 824 >>> u.configint(s, b'int2')
818 825 -42
819 826 >>> u.configint(s, b'unknown', 7)
820 827 7
821 828 >>> u.setconfig(s, b'invalid', b'somevalue')
822 829 >>> u.configint(s, b'invalid')
823 830 Traceback (most recent call last):
824 831 ...
825 832 ConfigError: foo.invalid is not a valid integer ('somevalue')
826 833 """
827 834
828 835 return self.configwith(
829 836 int, section, name, default, b'integer', untrusted
830 837 )
831 838
832 839 def configbytes(self, section, name, default=_unset, untrusted=False):
833 840 """parse a configuration element as a quantity in bytes
834 841
835 842 Units can be specified as b (bytes), k or kb (kilobytes), m or
836 843 mb (megabytes), g or gb (gigabytes).
837 844
838 845 >>> u = ui(); s = b'foo'
839 846 >>> u.setconfig(s, b'val1', b'42')
840 847 >>> u.configbytes(s, b'val1')
841 848 42
842 849 >>> u.setconfig(s, b'val2', b'42.5 kb')
843 850 >>> u.configbytes(s, b'val2')
844 851 43520
845 852 >>> u.configbytes(s, b'unknown', b'7 MB')
846 853 7340032
847 854 >>> u.setconfig(s, b'invalid', b'somevalue')
848 855 >>> u.configbytes(s, b'invalid')
849 856 Traceback (most recent call last):
850 857 ...
851 858 ConfigError: foo.invalid is not a byte quantity ('somevalue')
852 859 """
853 860
854 861 value = self._config(section, name, default, untrusted)
855 862 if value is _unset:
856 863 if default is _unset:
857 864 default = 0
858 865 value = default
859 866 if not isinstance(value, bytes):
860 867 return value
861 868 try:
862 869 return util.sizetoint(value)
863 870 except error.ParseError:
864 871 raise error.ConfigError(
865 872 _(b"%s.%s is not a byte quantity ('%s')")
866 873 % (section, name, value)
867 874 )
868 875
869 876 def configlist(self, section, name, default=_unset, untrusted=False):
870 877 """parse a configuration element as a list of comma/space separated
871 878 strings
872 879
873 880 >>> u = ui(); s = b'foo'
874 881 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
875 882 >>> u.configlist(s, b'list1')
876 883 ['this', 'is', 'a small', 'test']
877 884 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
878 885 >>> u.configlist(s, b'list2')
879 886 ['this', 'is', 'a small', 'test']
880 887 """
881 888 # default is not always a list
882 889 v = self.configwith(
883 890 config.parselist, section, name, default, b'list', untrusted
884 891 )
885 892 if isinstance(v, bytes):
886 893 return config.parselist(v)
887 894 elif v is None:
888 895 return []
889 896 return v
890 897
891 898 def configdate(self, section, name, default=_unset, untrusted=False):
892 899 """parse a configuration element as a tuple of ints
893 900
894 901 >>> u = ui(); s = b'foo'
895 902 >>> u.setconfig(s, b'date', b'0 0')
896 903 >>> u.configdate(s, b'date')
897 904 (0, 0)
898 905 """
899 906 if self.config(section, name, default, untrusted):
900 907 return self.configwith(
901 908 dateutil.parsedate, section, name, default, b'date', untrusted
902 909 )
903 910 if default is _unset:
904 911 return None
905 912 return default
906 913
907 914 def configdefault(self, section, name):
908 915 """returns the default value of the config item"""
909 916 item = self._knownconfig.get(section, {}).get(name)
910 917 itemdefault = None
911 918 if item is not None:
912 919 if callable(item.default):
913 920 itemdefault = item.default()
914 921 else:
915 922 itemdefault = item.default
916 923 return itemdefault
917 924
918 925 def hasconfig(self, section, name, untrusted=False):
919 926 return self._data(untrusted).hasitem(section, name)
920 927
921 928 def has_section(self, section, untrusted=False):
922 929 '''tell whether section exists in config.'''
923 930 return section in self._data(untrusted)
924 931
925 932 def configitems(self, section, untrusted=False, ignoresub=False):
926 933 items = self._data(untrusted).items(section)
927 934 if ignoresub:
928 935 items = [i for i in items if b':' not in i[0]]
929 936 if self.debugflag and not untrusted and self._reportuntrusted:
930 937 for k, v in self._ucfg.items(section):
931 938 if self._tcfg.get(section, k) != v:
932 939 self.debug(
933 940 b"ignoring untrusted configuration option "
934 941 b"%s.%s = %s\n" % (section, k, v)
935 942 )
936 943 return items
937 944
938 945 def walkconfig(self, untrusted=False):
939 946 cfg = self._data(untrusted)
940 947 for section in cfg.sections():
941 948 for name, value in self.configitems(section, untrusted):
942 949 yield section, name, value
943 950
944 951 def plain(self, feature=None):
945 952 """is plain mode active?
946 953
947 954 Plain mode means that all configuration variables which affect
948 955 the behavior and output of Mercurial should be
949 956 ignored. Additionally, the output should be stable,
950 957 reproducible and suitable for use in scripts or applications.
951 958
952 959 The only way to trigger plain mode is by setting either the
953 960 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
954 961
955 962 The return value can either be
956 963 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
957 964 - False if feature is disabled by default and not included in HGPLAIN
958 965 - True otherwise
959 966 """
960 967 if (
961 968 b'HGPLAIN' not in encoding.environ
962 969 and b'HGPLAINEXCEPT' not in encoding.environ
963 970 ):
964 971 return False
965 972 exceptions = (
966 973 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
967 974 )
968 975 # TODO: add support for HGPLAIN=+feature,-feature syntax
969 976 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
970 977 b','
971 978 ):
972 979 exceptions.append(b'strictflags')
973 980 if feature and exceptions:
974 981 return feature not in exceptions
975 982 return True
976 983
977 984 def username(self, acceptempty=False):
978 985 """Return default username to be used in commits.
979 986
980 987 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
981 988 and stop searching if one of these is set.
982 989 If not found and acceptempty is True, returns None.
983 990 If not found and ui.askusername is True, ask the user, else use
984 991 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
985 992 If no username could be found, raise an Abort error.
986 993 """
987 994 user = encoding.environ.get(b"HGUSER")
988 995 if user is None:
989 996 user = self.config(b"ui", b"username")
990 997 if user is not None:
991 998 user = os.path.expandvars(user)
992 999 if user is None:
993 1000 user = encoding.environ.get(b"EMAIL")
994 1001 if user is None and acceptempty:
995 1002 return user
996 1003 if user is None and self.configbool(b"ui", b"askusername"):
997 1004 user = self.prompt(_(b"enter a commit username:"), default=None)
998 1005 if user is None and not self.interactive():
999 1006 try:
1000 1007 user = b'%s@%s' % (
1001 1008 procutil.getuser(),
1002 1009 encoding.strtolocal(socket.getfqdn()),
1003 1010 )
1004 1011 self.warn(_(b"no username found, using '%s' instead\n") % user)
1005 1012 except KeyError:
1006 1013 pass
1007 1014 if not user:
1008 1015 raise error.Abort(
1009 1016 _(b'no username supplied'),
1010 1017 hint=_(b"use 'hg config --edit' " b'to set your username'),
1011 1018 )
1012 1019 if b"\n" in user:
1013 1020 raise error.Abort(
1014 1021 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
1015 1022 )
1016 1023 return user
1017 1024
1018 1025 def shortuser(self, user):
1019 1026 """Return a short representation of a user name or email address."""
1020 1027 if not self.verbose:
1021 1028 user = stringutil.shortuser(user)
1022 1029 return user
1023 1030
1024 1031 def expandpath(self, loc, default=None):
1025 1032 """Return repository location relative to cwd or from [paths]"""
1026 1033 try:
1027 1034 p = self.paths.getpath(loc)
1028 1035 if p:
1029 1036 return p.rawloc
1030 1037 except error.RepoError:
1031 1038 pass
1032 1039
1033 1040 if default:
1034 1041 try:
1035 1042 p = self.paths.getpath(default)
1036 1043 if p:
1037 1044 return p.rawloc
1038 1045 except error.RepoError:
1039 1046 pass
1040 1047
1041 1048 return loc
1042 1049
1043 1050 @util.propertycache
1044 1051 def paths(self):
1045 1052 return paths(self)
1046 1053
1047 1054 @property
1048 1055 def fout(self):
1049 1056 return self._fout
1050 1057
1051 1058 @fout.setter
1052 1059 def fout(self, f):
1053 1060 self._fout = f
1054 1061 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1055 1062
1056 1063 @property
1057 1064 def ferr(self):
1058 1065 return self._ferr
1059 1066
1060 1067 @ferr.setter
1061 1068 def ferr(self, f):
1062 1069 self._ferr = f
1063 1070 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1064 1071
1065 1072 @property
1066 1073 def fin(self):
1067 1074 return self._fin
1068 1075
1069 1076 @fin.setter
1070 1077 def fin(self, f):
1071 1078 self._fin = f
1072 1079
1073 1080 @property
1074 1081 def fmsg(self):
1075 1082 """Stream dedicated for status/error messages; may be None if
1076 1083 fout/ferr are used"""
1077 1084 return self._fmsg
1078 1085
1079 1086 @fmsg.setter
1080 1087 def fmsg(self, f):
1081 1088 self._fmsg = f
1082 1089 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1083 1090
1084 1091 def pushbuffer(self, error=False, subproc=False, labeled=False):
1085 1092 """install a buffer to capture standard output of the ui object
1086 1093
1087 1094 If error is True, the error output will be captured too.
1088 1095
1089 1096 If subproc is True, output from subprocesses (typically hooks) will be
1090 1097 captured too.
1091 1098
1092 1099 If labeled is True, any labels associated with buffered
1093 1100 output will be handled. By default, this has no effect
1094 1101 on the output returned, but extensions and GUI tools may
1095 1102 handle this argument and returned styled output. If output
1096 1103 is being buffered so it can be captured and parsed or
1097 1104 processed, labeled should not be set to True.
1098 1105 """
1099 1106 self._buffers.append([])
1100 1107 self._bufferstates.append((error, subproc, labeled))
1101 1108 self._bufferapplylabels = labeled
1102 1109
1103 1110 def popbuffer(self):
1104 1111 '''pop the last buffer and return the buffered output'''
1105 1112 self._bufferstates.pop()
1106 1113 if self._bufferstates:
1107 1114 self._bufferapplylabels = self._bufferstates[-1][2]
1108 1115 else:
1109 1116 self._bufferapplylabels = None
1110 1117
1111 1118 return b"".join(self._buffers.pop())
1112 1119
1113 1120 def _isbuffered(self, dest):
1114 1121 if dest is self._fout:
1115 1122 return bool(self._buffers)
1116 1123 if dest is self._ferr:
1117 1124 return bool(self._bufferstates and self._bufferstates[-1][0])
1118 1125 return False
1119 1126
1120 1127 def canwritewithoutlabels(self):
1121 1128 '''check if write skips the label'''
1122 1129 if self._buffers and not self._bufferapplylabels:
1123 1130 return True
1124 1131 return self._colormode is None
1125 1132
1126 1133 def canbatchlabeledwrites(self):
1127 1134 '''check if write calls with labels are batchable'''
1128 1135 # Windows color printing is special, see ``write``.
1129 1136 return self._colormode != b'win32'
1130 1137
1131 1138 def write(self, *args, **opts):
1132 1139 """write args to output
1133 1140
1134 1141 By default, this method simply writes to the buffer or stdout.
1135 1142 Color mode can be set on the UI class to have the output decorated
1136 1143 with color modifier before being written to stdout.
1137 1144
1138 1145 The color used is controlled by an optional keyword argument, "label".
1139 1146 This should be a string containing label names separated by space.
1140 1147 Label names take the form of "topic.type". For example, ui.debug()
1141 1148 issues a label of "ui.debug".
1142 1149
1143 1150 Progress reports via stderr are normally cleared before writing as
1144 1151 stdout and stderr go to the same terminal. This can be skipped with
1145 1152 the optional keyword argument "keepprogressbar". The progress bar
1146 1153 will continue to occupy a partial line on stderr in that case.
1147 1154 This functionality is intended when Mercurial acts as data source
1148 1155 in a pipe.
1149 1156
1150 1157 When labeling output for a specific command, a label of
1151 1158 "cmdname.type" is recommended. For example, status issues
1152 1159 a label of "status.modified" for modified files.
1153 1160 """
1154 1161 dest = self._fout
1155 1162
1156 1163 # inlined _write() for speed
1157 1164 if self._buffers:
1158 1165 label = opts.get('label', b'')
1159 1166 if label and self._bufferapplylabels:
1160 1167 self._buffers[-1].extend(self.label(a, label) for a in args)
1161 1168 else:
1162 1169 self._buffers[-1].extend(args)
1163 1170 return
1164 1171
1165 1172 # inlined _writenobuf() for speed
1166 1173 if not opts.get('keepprogressbar', False):
1167 1174 self._progclear()
1168 1175 msg = b''.join(args)
1169 1176
1170 1177 # opencode timeblockedsection because this is a critical path
1171 1178 starttime = util.timer()
1172 1179 try:
1173 1180 if self._colormode == b'win32':
1174 1181 # windows color printing is its own can of crab, defer to
1175 1182 # the color module and that is it.
1176 1183 color.win32print(self, dest.write, msg, **opts)
1177 1184 else:
1178 1185 if self._colormode is not None:
1179 1186 label = opts.get('label', b'')
1180 1187 msg = self.label(msg, label)
1181 1188 dest.write(msg)
1182 1189 except IOError as err:
1183 1190 raise error.StdioError(err)
1184 1191 finally:
1185 1192 self._blockedtimes[b'stdio_blocked'] += (
1186 1193 util.timer() - starttime
1187 1194 ) * 1000
1188 1195
1189 1196 def write_err(self, *args, **opts):
1190 1197 self._write(self._ferr, *args, **opts)
1191 1198
1192 1199 def _write(self, dest, *args, **opts):
1193 1200 # update write() as well if you touch this code
1194 1201 if self._isbuffered(dest):
1195 1202 label = opts.get('label', b'')
1196 1203 if label and self._bufferapplylabels:
1197 1204 self._buffers[-1].extend(self.label(a, label) for a in args)
1198 1205 else:
1199 1206 self._buffers[-1].extend(args)
1200 1207 else:
1201 1208 self._writenobuf(dest, *args, **opts)
1202 1209
1203 1210 def _writenobuf(self, dest, *args, **opts):
1204 1211 # update write() as well if you touch this code
1205 1212 if not opts.get('keepprogressbar', False):
1206 1213 self._progclear()
1207 1214 msg = b''.join(args)
1208 1215
1209 1216 # opencode timeblockedsection because this is a critical path
1210 1217 starttime = util.timer()
1211 1218 try:
1212 1219 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1213 1220 self._fout.flush()
1214 1221 if getattr(dest, 'structured', False):
1215 1222 # channel for machine-readable output with metadata, where
1216 1223 # no extra colorization is necessary.
1217 1224 dest.write(msg, **opts)
1218 1225 elif self._colormode == b'win32':
1219 1226 # windows color printing is its own can of crab, defer to
1220 1227 # the color module and that is it.
1221 1228 color.win32print(self, dest.write, msg, **opts)
1222 1229 else:
1223 1230 if self._colormode is not None:
1224 1231 label = opts.get('label', b'')
1225 1232 msg = self.label(msg, label)
1226 1233 dest.write(msg)
1227 1234 # stderr may be buffered under win32 when redirected to files,
1228 1235 # including stdout.
1229 1236 if dest is self._ferr and not getattr(dest, 'closed', False):
1230 1237 dest.flush()
1231 1238 except IOError as err:
1232 1239 if dest is self._ferr and err.errno in (
1233 1240 errno.EPIPE,
1234 1241 errno.EIO,
1235 1242 errno.EBADF,
1236 1243 ):
1237 1244 # no way to report the error, so ignore it
1238 1245 return
1239 1246 raise error.StdioError(err)
1240 1247 finally:
1241 1248 self._blockedtimes[b'stdio_blocked'] += (
1242 1249 util.timer() - starttime
1243 1250 ) * 1000
1244 1251
1245 1252 def _writemsg(self, dest, *args, **opts):
1246 1253 timestamp = self.showtimestamp and opts.get('type') in {
1247 1254 b'debug',
1248 1255 b'error',
1249 1256 b'note',
1250 1257 b'status',
1251 1258 b'warning',
1252 1259 }
1253 1260 if timestamp:
1254 1261 args = (
1255 1262 b'[%s] '
1256 1263 % pycompat.bytestr(datetime.datetime.now().isoformat()),
1257 1264 ) + args
1258 1265 _writemsgwith(self._write, dest, *args, **opts)
1259 1266 if timestamp:
1260 1267 dest.flush()
1261 1268
1262 1269 def _writemsgnobuf(self, dest, *args, **opts):
1263 1270 _writemsgwith(self._writenobuf, dest, *args, **opts)
1264 1271
1265 1272 def flush(self):
1266 1273 # opencode timeblockedsection because this is a critical path
1267 1274 starttime = util.timer()
1268 1275 try:
1269 1276 try:
1270 1277 self._fout.flush()
1271 1278 except IOError as err:
1272 1279 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1273 1280 raise error.StdioError(err)
1274 1281 finally:
1275 1282 try:
1276 1283 self._ferr.flush()
1277 1284 except IOError as err:
1278 1285 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1279 1286 raise error.StdioError(err)
1280 1287 finally:
1281 1288 self._blockedtimes[b'stdio_blocked'] += (
1282 1289 util.timer() - starttime
1283 1290 ) * 1000
1284 1291
1285 1292 def _isatty(self, fh):
1286 1293 if self.configbool(b'ui', b'nontty'):
1287 1294 return False
1288 1295 return procutil.isatty(fh)
1289 1296
1290 1297 def protectfinout(self):
1291 1298 """Duplicate ui streams and redirect original if they are stdio
1292 1299
1293 1300 Returns (fin, fout) which point to the original ui fds, but may be
1294 1301 copy of them. The returned streams can be considered "owned" in that
1295 1302 print(), exec(), etc. never reach to them.
1296 1303 """
1297 1304 if self._finoutredirected:
1298 1305 # if already redirected, protectstdio() would just create another
1299 1306 # nullfd pair, which is equivalent to returning self._fin/_fout.
1300 1307 return self._fin, self._fout
1301 1308 fin, fout = procutil.protectstdio(self._fin, self._fout)
1302 1309 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1303 1310 return fin, fout
1304 1311
1305 1312 def restorefinout(self, fin, fout):
1306 1313 """Restore ui streams from possibly duplicated (fin, fout)"""
1307 1314 if (fin, fout) == (self._fin, self._fout):
1308 1315 return
1309 1316 procutil.restorestdio(self._fin, self._fout, fin, fout)
1310 1317 # protectfinout() won't create more than one duplicated streams,
1311 1318 # so we can just turn the redirection flag off.
1312 1319 self._finoutredirected = False
1313 1320
1314 1321 @contextlib.contextmanager
1315 1322 def protectedfinout(self):
1316 1323 """Run code block with protected standard streams"""
1317 1324 fin, fout = self.protectfinout()
1318 1325 try:
1319 1326 yield fin, fout
1320 1327 finally:
1321 1328 self.restorefinout(fin, fout)
1322 1329
1323 1330 def disablepager(self):
1324 1331 self._disablepager = True
1325 1332
1326 1333 def pager(self, command):
1327 1334 """Start a pager for subsequent command output.
1328 1335
1329 1336 Commands which produce a long stream of output should call
1330 1337 this function to activate the user's preferred pagination
1331 1338 mechanism (which may be no pager). Calling this function
1332 1339 precludes any future use of interactive functionality, such as
1333 1340 prompting the user or activating curses.
1334 1341
1335 1342 Args:
1336 1343 command: The full, non-aliased name of the command. That is, "log"
1337 1344 not "history, "summary" not "summ", etc.
1338 1345 """
1339 1346 if self._disablepager or self.pageractive:
1340 1347 # how pager should do is already determined
1341 1348 return
1342 1349
1343 1350 if not command.startswith(b'internal-always-') and (
1344 1351 # explicit --pager=on (= 'internal-always-' prefix) should
1345 1352 # take precedence over disabling factors below
1346 1353 command in self.configlist(b'pager', b'ignore')
1347 1354 or not self.configbool(b'ui', b'paginate')
1348 1355 or not self.configbool(b'pager', b'attend-' + command, True)
1349 1356 or encoding.environ.get(b'TERM') == b'dumb'
1350 1357 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1351 1358 # formatted() will need some adjustment.
1352 1359 or not self.formatted()
1353 1360 or self.plain()
1354 1361 or self._buffers
1355 1362 # TODO: expose debugger-enabled on the UI object
1356 1363 or b'--debugger' in pycompat.sysargv
1357 1364 ):
1358 1365 # We only want to paginate if the ui appears to be
1359 1366 # interactive, the user didn't say HGPLAIN or
1360 1367 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1361 1368 return
1362 1369
1363 1370 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1364 1371 if not pagercmd:
1365 1372 return
1366 1373
1367 1374 pagerenv = {}
1368 1375 for name, value in rcutil.defaultpagerenv().items():
1369 1376 if name not in encoding.environ:
1370 1377 pagerenv[name] = value
1371 1378
1372 1379 self.debug(
1373 1380 b'starting pager for command %s\n' % stringutil.pprint(command)
1374 1381 )
1375 1382 self.flush()
1376 1383
1377 1384 wasformatted = self.formatted()
1378 1385 if util.safehasattr(signal, b"SIGPIPE"):
1379 1386 signal.signal(signal.SIGPIPE, _catchterm)
1380 1387 if self._runpager(pagercmd, pagerenv):
1381 1388 self.pageractive = True
1382 1389 # Preserve the formatted-ness of the UI. This is important
1383 1390 # because we mess with stdout, which might confuse
1384 1391 # auto-detection of things being formatted.
1385 1392 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1386 1393 self.setconfig(b'ui', b'interactive', False, b'pager')
1387 1394
1388 1395 # If pagermode differs from color.mode, reconfigure color now that
1389 1396 # pageractive is set.
1390 1397 cm = self._colormode
1391 1398 if cm != self.config(b'color', b'pagermode', cm):
1392 1399 color.setup(self)
1393 1400 else:
1394 1401 # If the pager can't be spawned in dispatch when --pager=on is
1395 1402 # given, don't try again when the command runs, to avoid a duplicate
1396 1403 # warning about a missing pager command.
1397 1404 self.disablepager()
1398 1405
1399 1406 def _runpager(self, command, env=None):
1400 1407 """Actually start the pager and set up file descriptors.
1401 1408
1402 1409 This is separate in part so that extensions (like chg) can
1403 1410 override how a pager is invoked.
1404 1411 """
1405 1412 if command == b'cat':
1406 1413 # Save ourselves some work.
1407 1414 return False
1408 1415 # If the command doesn't contain any of these characters, we
1409 1416 # assume it's a binary and exec it directly. This means for
1410 1417 # simple pager command configurations, we can degrade
1411 1418 # gracefully and tell the user about their broken pager.
1412 1419 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1413 1420
1414 1421 if pycompat.iswindows and not shell:
1415 1422 # Window's built-in `more` cannot be invoked with shell=False, but
1416 1423 # its `more.com` can. Hide this implementation detail from the
1417 1424 # user so we can also get sane bad PAGER behavior. MSYS has
1418 1425 # `more.exe`, so do a cmd.exe style resolution of the executable to
1419 1426 # determine which one to use.
1420 1427 fullcmd = procutil.findexe(command)
1421 1428 if not fullcmd:
1422 1429 self.warn(
1423 1430 _(b"missing pager command '%s', skipping pager\n") % command
1424 1431 )
1425 1432 return False
1426 1433
1427 1434 command = fullcmd
1428 1435
1429 1436 try:
1430 1437 pager = subprocess.Popen(
1431 1438 procutil.tonativestr(command),
1432 1439 shell=shell,
1433 1440 bufsize=-1,
1434 1441 close_fds=procutil.closefds,
1435 1442 stdin=subprocess.PIPE,
1436 1443 stdout=procutil.stdout,
1437 1444 stderr=procutil.stderr,
1438 1445 env=procutil.tonativeenv(procutil.shellenviron(env)),
1439 1446 )
1440 1447 except OSError as e:
1441 1448 if e.errno == errno.ENOENT and not shell:
1442 1449 self.warn(
1443 1450 _(b"missing pager command '%s', skipping pager\n") % command
1444 1451 )
1445 1452 return False
1446 1453 raise
1447 1454
1448 1455 # back up original file descriptors
1449 1456 stdoutfd = os.dup(procutil.stdout.fileno())
1450 1457 stderrfd = os.dup(procutil.stderr.fileno())
1451 1458
1452 1459 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1453 1460 if self._isatty(procutil.stderr):
1454 1461 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1455 1462
1456 1463 @self.atexit
1457 1464 def killpager():
1458 1465 if util.safehasattr(signal, b"SIGINT"):
1459 1466 signal.signal(signal.SIGINT, signal.SIG_IGN)
1460 1467 # restore original fds, closing pager.stdin copies in the process
1461 1468 os.dup2(stdoutfd, procutil.stdout.fileno())
1462 1469 os.dup2(stderrfd, procutil.stderr.fileno())
1463 1470 pager.stdin.close()
1464 1471 pager.wait()
1465 1472
1466 1473 return True
1467 1474
1468 1475 @property
1469 1476 def _exithandlers(self):
1470 1477 return _reqexithandlers
1471 1478
1472 1479 def atexit(self, func, *args, **kwargs):
1473 1480 """register a function to run after dispatching a request
1474 1481
1475 1482 Handlers do not stay registered across request boundaries."""
1476 1483 self._exithandlers.append((func, args, kwargs))
1477 1484 return func
1478 1485
1479 1486 def interface(self, feature):
1480 1487 """what interface to use for interactive console features?
1481 1488
1482 1489 The interface is controlled by the value of `ui.interface` but also by
1483 1490 the value of feature-specific configuration. For example:
1484 1491
1485 1492 ui.interface.histedit = text
1486 1493 ui.interface.chunkselector = curses
1487 1494
1488 1495 Here the features are "histedit" and "chunkselector".
1489 1496
1490 1497 The configuration above means that the default interfaces for commands
1491 1498 is curses, the interface for histedit is text and the interface for
1492 1499 selecting chunk is crecord (the best curses interface available).
1493 1500
1494 1501 Consider the following example:
1495 1502 ui.interface = curses
1496 1503 ui.interface.histedit = text
1497 1504
1498 1505 Then histedit will use the text interface and chunkselector will use
1499 1506 the default curses interface (crecord at the moment).
1500 1507 """
1501 1508 alldefaults = frozenset([b"text", b"curses"])
1502 1509
1503 1510 featureinterfaces = {
1504 1511 b"chunkselector": [
1505 1512 b"text",
1506 1513 b"curses",
1507 1514 ],
1508 1515 b"histedit": [
1509 1516 b"text",
1510 1517 b"curses",
1511 1518 ],
1512 1519 }
1513 1520
1514 1521 # Feature-specific interface
1515 1522 if feature not in featureinterfaces.keys():
1516 1523 # Programming error, not user error
1517 1524 raise ValueError(b"Unknown feature requested %s" % feature)
1518 1525
1519 1526 availableinterfaces = frozenset(featureinterfaces[feature])
1520 1527 if alldefaults > availableinterfaces:
1521 1528 # Programming error, not user error. We need a use case to
1522 1529 # define the right thing to do here.
1523 1530 raise ValueError(
1524 1531 b"Feature %s does not handle all default interfaces" % feature
1525 1532 )
1526 1533
1527 1534 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1528 1535 return b"text"
1529 1536
1530 1537 # Default interface for all the features
1531 1538 defaultinterface = b"text"
1532 1539 i = self.config(b"ui", b"interface")
1533 1540 if i in alldefaults:
1534 1541 defaultinterface = i
1535 1542
1536 1543 choseninterface = defaultinterface
1537 1544 f = self.config(b"ui", b"interface.%s" % feature)
1538 1545 if f in availableinterfaces:
1539 1546 choseninterface = f
1540 1547
1541 1548 if i is not None and defaultinterface != i:
1542 1549 if f is not None:
1543 1550 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1544 1551 else:
1545 1552 self.warn(
1546 1553 _(b"invalid value for ui.interface: %s (using %s)\n")
1547 1554 % (i, choseninterface)
1548 1555 )
1549 1556 if f is not None and choseninterface != f:
1550 1557 self.warn(
1551 1558 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1552 1559 % (feature, f, choseninterface)
1553 1560 )
1554 1561
1555 1562 return choseninterface
1556 1563
1557 1564 def interactive(self):
1558 1565 """is interactive input allowed?
1559 1566
1560 1567 An interactive session is a session where input can be reasonably read
1561 1568 from `sys.stdin'. If this function returns false, any attempt to read
1562 1569 from stdin should fail with an error, unless a sensible default has been
1563 1570 specified.
1564 1571
1565 1572 Interactiveness is triggered by the value of the `ui.interactive'
1566 1573 configuration variable or - if it is unset - when `sys.stdin' points
1567 1574 to a terminal device.
1568 1575
1569 1576 This function refers to input only; for output, see `ui.formatted()'.
1570 1577 """
1571 1578 i = self.configbool(b"ui", b"interactive")
1572 1579 if i is None:
1573 1580 # some environments replace stdin without implementing isatty
1574 1581 # usually those are non-interactive
1575 1582 return self._isatty(self._fin)
1576 1583
1577 1584 return i
1578 1585
1579 1586 def termwidth(self):
1580 1587 """how wide is the terminal in columns?"""
1581 1588 if b'COLUMNS' in encoding.environ:
1582 1589 try:
1583 1590 return int(encoding.environ[b'COLUMNS'])
1584 1591 except ValueError:
1585 1592 pass
1586 1593 return scmutil.termsize(self)[0]
1587 1594
1588 1595 def formatted(self):
1589 1596 """should formatted output be used?
1590 1597
1591 1598 It is often desirable to format the output to suite the output medium.
1592 1599 Examples of this are truncating long lines or colorizing messages.
1593 1600 However, this is not often not desirable when piping output into other
1594 1601 utilities, e.g. `grep'.
1595 1602
1596 1603 Formatted output is triggered by the value of the `ui.formatted'
1597 1604 configuration variable or - if it is unset - when `sys.stdout' points
1598 1605 to a terminal device. Please note that `ui.formatted' should be
1599 1606 considered an implementation detail; it is not intended for use outside
1600 1607 Mercurial or its extensions.
1601 1608
1602 1609 This function refers to output only; for input, see `ui.interactive()'.
1603 1610 This function always returns false when in plain mode, see `ui.plain()'.
1604 1611 """
1605 1612 if self.plain():
1606 1613 return False
1607 1614
1608 1615 i = self.configbool(b"ui", b"formatted")
1609 1616 if i is None:
1610 1617 # some environments replace stdout without implementing isatty
1611 1618 # usually those are non-interactive
1612 1619 return self._isatty(self._fout)
1613 1620
1614 1621 return i
1615 1622
1616 1623 def _readline(self, prompt=b' ', promptopts=None):
1617 1624 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1618 1625 # because they have to be text streams with *no buffering*. Instead,
1619 1626 # we use rawinput() only if call_readline() will be invoked by
1620 1627 # PyOS_Readline(), so no I/O will be made at Python layer.
1621 1628 usereadline = (
1622 1629 self._isatty(self._fin)
1623 1630 and self._isatty(self._fout)
1624 1631 and procutil.isstdin(self._fin)
1625 1632 and procutil.isstdout(self._fout)
1626 1633 )
1627 1634 if usereadline:
1628 1635 try:
1629 1636 # magically add command line editing support, where
1630 1637 # available
1631 1638 import readline
1632 1639
1633 1640 # force demandimport to really load the module
1634 1641 readline.read_history_file
1635 1642 # windows sometimes raises something other than ImportError
1636 1643 except Exception:
1637 1644 usereadline = False
1638 1645
1639 1646 if self._colormode == b'win32' or not usereadline:
1640 1647 if not promptopts:
1641 1648 promptopts = {}
1642 1649 self._writemsgnobuf(
1643 1650 self._fmsgout, prompt, type=b'prompt', **promptopts
1644 1651 )
1645 1652 self.flush()
1646 1653 prompt = b' '
1647 1654 else:
1648 1655 prompt = self.label(prompt, b'ui.prompt') + b' '
1649 1656
1650 1657 # prompt ' ' must exist; otherwise readline may delete entire line
1651 1658 # - http://bugs.python.org/issue12833
1652 1659 with self.timeblockedsection(b'stdio'):
1653 1660 if usereadline:
1654 1661 self.flush()
1655 1662 prompt = encoding.strfromlocal(prompt)
1656 1663 line = encoding.strtolocal(pycompat.rawinput(prompt))
1657 1664 # When stdin is in binary mode on Windows, it can cause
1658 1665 # raw_input() to emit an extra trailing carriage return
1659 1666 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1660 1667 line = line[:-1]
1661 1668 else:
1662 1669 self._fout.write(pycompat.bytestr(prompt))
1663 1670 self._fout.flush()
1664 1671 line = self._fin.readline()
1665 1672 if not line:
1666 1673 raise EOFError
1667 1674 line = line.rstrip(pycompat.oslinesep)
1668 1675
1669 1676 return line
1670 1677
1671 1678 def prompt(self, msg, default=b"y"):
1672 1679 """Prompt user with msg, read response.
1673 1680 If ui is not interactive, the default is returned.
1674 1681 """
1675 1682 return self._prompt(msg, default=default)
1676 1683
1677 1684 def _prompt(self, msg, **opts):
1678 1685 default = opts['default']
1679 1686 if not self.interactive():
1680 1687 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1681 1688 self._writemsg(
1682 1689 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1683 1690 )
1684 1691 return default
1685 1692 try:
1686 1693 r = self._readline(prompt=msg, promptopts=opts)
1687 1694 if not r:
1688 1695 r = default
1689 1696 if self.configbool(b'ui', b'promptecho'):
1690 1697 self._writemsg(
1691 1698 self._fmsgout, r or b'', b"\n", type=b'promptecho'
1692 1699 )
1693 1700 return r
1694 1701 except EOFError:
1695 1702 raise error.ResponseExpected()
1696 1703
1697 1704 @staticmethod
1698 1705 def extractchoices(prompt):
1699 1706 """Extract prompt message and list of choices from specified prompt.
1700 1707
1701 1708 This returns tuple "(message, choices)", and "choices" is the
1702 1709 list of tuple "(response character, text without &)".
1703 1710
1704 1711 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1705 1712 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1706 1713 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1707 1714 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1708 1715 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1709 1716 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1710 1717 """
1711 1718
1712 1719 # Sadly, the prompt string may have been built with a filename
1713 1720 # containing "$$" so let's try to find the first valid-looking
1714 1721 # prompt to start parsing. Sadly, we also can't rely on
1715 1722 # choices containing spaces, ASCII, or basically anything
1716 1723 # except an ampersand followed by a character.
1717 1724 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1718 1725 msg = m.group(1)
1719 1726 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1720 1727
1721 1728 def choicetuple(s):
1722 1729 ampidx = s.index(b'&')
1723 1730 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1724 1731
1725 1732 return (msg, [choicetuple(s) for s in choices])
1726 1733
1727 1734 def promptchoice(self, prompt, default=0):
1728 1735 """Prompt user with a message, read response, and ensure it matches
1729 1736 one of the provided choices. The prompt is formatted as follows:
1730 1737
1731 1738 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1732 1739
1733 1740 The index of the choice is returned. Responses are case
1734 1741 insensitive. If ui is not interactive, the default is
1735 1742 returned.
1736 1743 """
1737 1744
1738 1745 msg, choices = self.extractchoices(prompt)
1739 1746 resps = [r for r, t in choices]
1740 1747 while True:
1741 1748 r = self._prompt(msg, default=resps[default], choices=choices)
1742 1749 if r.lower() in resps:
1743 1750 return resps.index(r.lower())
1744 1751 # TODO: shouldn't it be a warning?
1745 1752 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1746 1753
1747 1754 def getpass(self, prompt=None, default=None):
1748 1755 if not self.interactive():
1749 1756 return default
1750 1757 try:
1751 1758 self._writemsg(
1752 1759 self._fmsgerr,
1753 1760 prompt or _(b'password: '),
1754 1761 type=b'prompt',
1755 1762 password=True,
1756 1763 )
1757 1764 # disable getpass() only if explicitly specified. it's still valid
1758 1765 # to interact with tty even if fin is not a tty.
1759 1766 with self.timeblockedsection(b'stdio'):
1760 1767 if self.configbool(b'ui', b'nontty'):
1761 1768 l = self._fin.readline()
1762 1769 if not l:
1763 1770 raise EOFError
1764 1771 return l.rstrip(b'\n')
1765 1772 else:
1766 1773 return encoding.strtolocal(getpass.getpass(''))
1767 1774 except EOFError:
1768 1775 raise error.ResponseExpected()
1769 1776
1770 1777 def status(self, *msg, **opts):
1771 1778 """write status message to output (if ui.quiet is False)
1772 1779
1773 1780 This adds an output label of "ui.status".
1774 1781 """
1775 1782 if not self.quiet:
1776 1783 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1777 1784
1778 1785 def warn(self, *msg, **opts):
1779 1786 """write warning message to output (stderr)
1780 1787
1781 1788 This adds an output label of "ui.warning".
1782 1789 """
1783 1790 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1784 1791
1785 1792 def error(self, *msg, **opts):
1786 1793 """write error message to output (stderr)
1787 1794
1788 1795 This adds an output label of "ui.error".
1789 1796 """
1790 1797 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1791 1798
1792 1799 def note(self, *msg, **opts):
1793 1800 """write note to output (if ui.verbose is True)
1794 1801
1795 1802 This adds an output label of "ui.note".
1796 1803 """
1797 1804 if self.verbose:
1798 1805 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1799 1806
1800 1807 def debug(self, *msg, **opts):
1801 1808 """write debug message to output (if ui.debugflag is True)
1802 1809
1803 1810 This adds an output label of "ui.debug".
1804 1811 """
1805 1812 if self.debugflag:
1806 1813 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1807 1814 self.log(b'debug', b'%s', b''.join(msg))
1808 1815
1809 1816 # Aliases to defeat check-code.
1810 1817 statusnoi18n = status
1811 1818 notenoi18n = note
1812 1819 warnnoi18n = warn
1813 1820 writenoi18n = write
1814 1821
1815 1822 def edit(
1816 1823 self,
1817 1824 text,
1818 1825 user,
1819 1826 extra=None,
1820 1827 editform=None,
1821 1828 pending=None,
1822 1829 repopath=None,
1823 1830 action=None,
1824 1831 ):
1825 1832 if action is None:
1826 1833 self.develwarn(
1827 1834 b'action is None but will soon be a required '
1828 1835 b'parameter to ui.edit()'
1829 1836 )
1830 1837 extra_defaults = {
1831 1838 b'prefix': b'editor',
1832 1839 b'suffix': b'.txt',
1833 1840 }
1834 1841 if extra is not None:
1835 1842 if extra.get(b'suffix') is not None:
1836 1843 self.develwarn(
1837 1844 b'extra.suffix is not None but will soon be '
1838 1845 b'ignored by ui.edit()'
1839 1846 )
1840 1847 extra_defaults.update(extra)
1841 1848 extra = extra_defaults
1842 1849
1843 1850 if action == b'diff':
1844 1851 suffix = b'.diff'
1845 1852 elif action:
1846 1853 suffix = b'.%s.hg.txt' % action
1847 1854 else:
1848 1855 suffix = extra[b'suffix']
1849 1856
1850 1857 rdir = None
1851 1858 if self.configbool(b'experimental', b'editortmpinhg'):
1852 1859 rdir = repopath
1853 1860 (fd, name) = pycompat.mkstemp(
1854 1861 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1855 1862 )
1856 1863 try:
1857 1864 with os.fdopen(fd, 'wb') as f:
1858 1865 f.write(util.tonativeeol(text))
1859 1866
1860 1867 environ = {b'HGUSER': user}
1861 1868 if b'transplant_source' in extra:
1862 1869 environ.update(
1863 1870 {b'HGREVISION': hex(extra[b'transplant_source'])}
1864 1871 )
1865 1872 for label in (b'intermediate-source', b'source', b'rebase_source'):
1866 1873 if label in extra:
1867 1874 environ.update({b'HGREVISION': extra[label]})
1868 1875 break
1869 1876 if editform:
1870 1877 environ.update({b'HGEDITFORM': editform})
1871 1878 if pending:
1872 1879 environ.update({b'HG_PENDING': pending})
1873 1880
1874 1881 editor = self.geteditor()
1875 1882
1876 1883 self.system(
1877 1884 b"%s \"%s\"" % (editor, name),
1878 1885 environ=environ,
1879 1886 onerr=error.CanceledError,
1880 1887 errprefix=_(b"edit failed"),
1881 1888 blockedtag=b'editor',
1882 1889 )
1883 1890
1884 1891 with open(name, 'rb') as f:
1885 1892 t = util.fromnativeeol(f.read())
1886 1893 finally:
1887 1894 os.unlink(name)
1888 1895
1889 1896 return t
1890 1897
1891 1898 def system(
1892 1899 self,
1893 1900 cmd,
1894 1901 environ=None,
1895 1902 cwd=None,
1896 1903 onerr=None,
1897 1904 errprefix=None,
1898 1905 blockedtag=None,
1899 1906 ):
1900 1907 """execute shell command with appropriate output stream. command
1901 1908 output will be redirected if fout is not stdout.
1902 1909
1903 1910 if command fails and onerr is None, return status, else raise onerr
1904 1911 object as exception.
1905 1912 """
1906 1913 if blockedtag is None:
1907 1914 # Long cmds tend to be because of an absolute path on cmd. Keep
1908 1915 # the tail end instead
1909 1916 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1910 1917 blockedtag = b'unknown_system_' + cmdsuffix
1911 1918 out = self._fout
1912 1919 if any(s[1] for s in self._bufferstates):
1913 1920 out = self
1914 1921 with self.timeblockedsection(blockedtag):
1915 1922 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1916 1923 if rc and onerr:
1917 1924 errmsg = b'%s %s' % (
1918 1925 procutil.shellsplit(cmd)[0],
1919 1926 procutil.explainexit(rc),
1920 1927 )
1921 1928 if errprefix:
1922 1929 errmsg = b'%s: %s' % (errprefix, errmsg)
1923 1930 raise onerr(errmsg)
1924 1931 return rc
1925 1932
1926 1933 def _runsystem(self, cmd, environ, cwd, out):
1927 1934 """actually execute the given shell command (can be overridden by
1928 1935 extensions like chg)"""
1929 1936 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1930 1937
1931 1938 def traceback(self, exc=None, force=False):
1932 1939 """print exception traceback if traceback printing enabled or forced.
1933 1940 only to call in exception handler. returns true if traceback
1934 1941 printed."""
1935 1942 if self.tracebackflag or force:
1936 1943 if exc is None:
1937 1944 exc = sys.exc_info()
1938 1945 cause = getattr(exc[1], 'cause', None)
1939 1946
1940 1947 if cause is not None:
1941 1948 causetb = traceback.format_tb(cause[2])
1942 1949 exctb = traceback.format_tb(exc[2])
1943 1950 exconly = traceback.format_exception_only(cause[0], cause[1])
1944 1951
1945 1952 # exclude frame where 'exc' was chained and rethrown from exctb
1946 1953 self.write_err(
1947 1954 b'Traceback (most recent call last):\n',
1948 1955 encoding.strtolocal(''.join(exctb[:-1])),
1949 1956 encoding.strtolocal(''.join(causetb)),
1950 1957 encoding.strtolocal(''.join(exconly)),
1951 1958 )
1952 1959 else:
1953 1960 output = traceback.format_exception(exc[0], exc[1], exc[2])
1954 1961 self.write_err(encoding.strtolocal(''.join(output)))
1955 1962 return self.tracebackflag or force
1956 1963
1957 1964 def geteditor(self):
1958 1965 '''return editor to use'''
1959 1966 if pycompat.sysplatform == b'plan9':
1960 1967 # vi is the MIPS instruction simulator on Plan 9. We
1961 1968 # instead default to E to plumb commit messages to
1962 1969 # avoid confusion.
1963 1970 editor = b'E'
1964 1971 elif pycompat.isdarwin:
1965 1972 # vi on darwin is POSIX compatible to a fault, and that includes
1966 1973 # exiting non-zero if you make any mistake when running an ex
1967 1974 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
1968 1975 # while s/vi/vim/ doesn't.
1969 1976 editor = b'vim'
1970 1977 else:
1971 1978 editor = b'vi'
1972 1979 return encoding.environ.get(b"HGEDITOR") or self.config(
1973 1980 b"ui", b"editor", editor
1974 1981 )
1975 1982
1976 1983 @util.propertycache
1977 1984 def _progbar(self):
1978 1985 """setup the progbar singleton to the ui object"""
1979 1986 if (
1980 1987 self.quiet
1981 1988 or self.debugflag
1982 1989 or self.configbool(b'progress', b'disable')
1983 1990 or not progress.shouldprint(self)
1984 1991 ):
1985 1992 return None
1986 1993 return getprogbar(self)
1987 1994
1988 1995 def _progclear(self):
1989 1996 """clear progress bar output if any. use it before any output"""
1990 1997 if not haveprogbar(): # nothing loaded yet
1991 1998 return
1992 1999 if self._progbar is not None and self._progbar.printed:
1993 2000 self._progbar.clear()
1994 2001
1995 2002 def makeprogress(self, topic, unit=b"", total=None):
1996 2003 """Create a progress helper for the specified topic"""
1997 2004 if getattr(self._fmsgerr, 'structured', False):
1998 2005 # channel for machine-readable output with metadata, just send
1999 2006 # raw information
2000 2007 # TODO: consider porting some useful information (e.g. estimated
2001 2008 # time) from progbar. we might want to support update delay to
2002 2009 # reduce the cost of transferring progress messages.
2003 2010 def updatebar(topic, pos, item, unit, total):
2004 2011 self._fmsgerr.write(
2005 2012 None,
2006 2013 type=b'progress',
2007 2014 topic=topic,
2008 2015 pos=pos,
2009 2016 item=item,
2010 2017 unit=unit,
2011 2018 total=total,
2012 2019 )
2013 2020
2014 2021 elif self._progbar is not None:
2015 2022 updatebar = self._progbar.progress
2016 2023 else:
2017 2024
2018 2025 def updatebar(topic, pos, item, unit, total):
2019 2026 pass
2020 2027
2021 2028 return scmutil.progress(self, updatebar, topic, unit, total)
2022 2029
2023 2030 def getlogger(self, name):
2024 2031 """Returns a logger of the given name; or None if not registered"""
2025 2032 return self._loggers.get(name)
2026 2033
2027 2034 def setlogger(self, name, logger):
2028 2035 """Install logger which can be identified later by the given name
2029 2036
2030 2037 More than one loggers can be registered. Use extension or module
2031 2038 name to uniquely identify the logger instance.
2032 2039 """
2033 2040 self._loggers[name] = logger
2034 2041
2035 2042 def log(self, event, msgfmt, *msgargs, **opts):
2036 2043 """hook for logging facility extensions
2037 2044
2038 2045 event should be a readily-identifiable subsystem, which will
2039 2046 allow filtering.
2040 2047
2041 2048 msgfmt should be a newline-terminated format string to log, and
2042 2049 *msgargs are %-formatted into it.
2043 2050
2044 2051 **opts currently has no defined meanings.
2045 2052 """
2046 2053 if not self._loggers:
2047 2054 return
2048 2055 activeloggers = [
2049 2056 l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
2050 2057 ]
2051 2058 if not activeloggers:
2052 2059 return
2053 2060 msg = msgfmt % msgargs
2054 2061 opts = pycompat.byteskwargs(opts)
2055 2062 # guard against recursion from e.g. ui.debug()
2056 2063 registeredloggers = self._loggers
2057 2064 self._loggers = {}
2058 2065 try:
2059 2066 for logger in activeloggers:
2060 2067 logger.log(self, event, msg, opts)
2061 2068 finally:
2062 2069 self._loggers = registeredloggers
2063 2070
2064 2071 def label(self, msg, label):
2065 2072 """style msg based on supplied label
2066 2073
2067 2074 If some color mode is enabled, this will add the necessary control
2068 2075 characters to apply such color. In addition, 'debug' color mode adds
2069 2076 markup showing which label affects a piece of text.
2070 2077
2071 2078 ui.write(s, 'label') is equivalent to
2072 2079 ui.write(ui.label(s, 'label')).
2073 2080 """
2074 2081 if self._colormode is not None:
2075 2082 return color.colorlabel(self, msg, label)
2076 2083 return msg
2077 2084
2078 2085 def develwarn(self, msg, stacklevel=1, config=None):
2079 2086 """issue a developer warning message
2080 2087
2081 2088 Use 'stacklevel' to report the offender some layers further up in the
2082 2089 stack.
2083 2090 """
2084 2091 if not self.configbool(b'devel', b'all-warnings'):
2085 2092 if config is None or not self.configbool(b'devel', config):
2086 2093 return
2087 2094 msg = b'devel-warn: ' + msg
2088 2095 stacklevel += 1 # get in develwarn
2089 2096 if self.tracebackflag:
2090 2097 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2091 2098 self.log(
2092 2099 b'develwarn',
2093 2100 b'%s at:\n%s'
2094 2101 % (msg, b''.join(util.getstackframes(stacklevel))),
2095 2102 )
2096 2103 else:
2097 2104 curframe = inspect.currentframe()
2098 2105 calframe = inspect.getouterframes(curframe, 2)
2099 2106 fname, lineno, fmsg = calframe[stacklevel][1:4]
2100 2107 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2101 2108 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2102 2109 self.log(
2103 2110 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2104 2111 )
2105 2112
2106 2113 # avoid cycles
2107 2114 del curframe
2108 2115 del calframe
2109 2116
2110 2117 def deprecwarn(self, msg, version, stacklevel=2):
2111 2118 """issue a deprecation warning
2112 2119
2113 2120 - msg: message explaining what is deprecated and how to upgrade,
2114 2121 - version: last version where the API will be supported,
2115 2122 """
2116 2123 if not (
2117 2124 self.configbool(b'devel', b'all-warnings')
2118 2125 or self.configbool(b'devel', b'deprec-warn')
2119 2126 ):
2120 2127 return
2121 2128 msg += (
2122 2129 b"\n(compatibility will be dropped after Mercurial-%s,"
2123 2130 b" update your code.)"
2124 2131 ) % version
2125 2132 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2126 2133
2127 2134 def exportableenviron(self):
2128 2135 """The environment variables that are safe to export, e.g. through
2129 2136 hgweb.
2130 2137 """
2131 2138 return self._exportableenviron
2132 2139
2133 2140 @contextlib.contextmanager
2134 2141 def configoverride(self, overrides, source=b""):
2135 2142 """Context manager for temporary config overrides
2136 2143 `overrides` must be a dict of the following structure:
2137 2144 {(section, name) : value}"""
2138 2145 backups = {}
2139 2146 try:
2140 2147 for (section, name), value in overrides.items():
2141 2148 backups[(section, name)] = self.backupconfig(section, name)
2142 2149 self.setconfig(section, name, value, source)
2143 2150 yield
2144 2151 finally:
2145 2152 for __, backup in backups.items():
2146 2153 self.restoreconfig(backup)
2147 2154 # just restoring ui.quiet config to the previous value is not enough
2148 2155 # as it does not update ui.quiet class member
2149 2156 if (b'ui', b'quiet') in overrides:
2150 2157 self.fixconfig(section=b'ui')
2151 2158
2152 2159 def estimatememory(self):
2153 2160 """Provide an estimate for the available system memory in Bytes.
2154 2161
2155 2162 This can be overriden via ui.available-memory. It returns None, if
2156 2163 no estimate can be computed.
2157 2164 """
2158 2165 value = self.config(b'ui', b'available-memory')
2159 2166 if value is not None:
2160 2167 try:
2161 2168 return util.sizetoint(value)
2162 2169 except error.ParseError:
2163 2170 raise error.ConfigError(
2164 2171 _(b"ui.available-memory value is invalid ('%s')") % value
2165 2172 )
2166 2173 return util._estimatememory()
2167 2174
2168 2175
2169 2176 class paths(dict):
2170 2177 """Represents a collection of paths and their configs.
2171 2178
2172 2179 Data is initially derived from ui instances and the config files they have
2173 2180 loaded.
2174 2181 """
2175 2182
2176 2183 def __init__(self, ui):
2177 2184 dict.__init__(self)
2178 2185
2179 2186 for name, loc in ui.configitems(b'paths', ignoresub=True):
2180 2187 # No location is the same as not existing.
2181 2188 if not loc:
2182 2189 continue
2183 2190 loc, sub = ui.configsuboptions(b'paths', name)
2184 2191 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
2185 2192
2186 2193 def getpath(self, name, default=None):
2187 2194 """Return a ``path`` from a string, falling back to default.
2188 2195
2189 2196 ``name`` can be a named path or locations. Locations are filesystem
2190 2197 paths or URIs.
2191 2198
2192 2199 Returns None if ``name`` is not a registered path, a URI, or a local
2193 2200 path to a repo.
2194 2201 """
2195 2202 # Only fall back to default if no path was requested.
2196 2203 if name is None:
2197 2204 if not default:
2198 2205 default = ()
2199 2206 elif not isinstance(default, (tuple, list)):
2200 2207 default = (default,)
2201 2208 for k in default:
2202 2209 try:
2203 2210 return self[k]
2204 2211 except KeyError:
2205 2212 continue
2206 2213 return None
2207 2214
2208 2215 # Most likely empty string.
2209 2216 # This may need to raise in the future.
2210 2217 if not name:
2211 2218 return None
2212 2219
2213 2220 try:
2214 2221 return self[name]
2215 2222 except KeyError:
2216 2223 # Try to resolve as a local path or URI.
2217 2224 try:
2218 2225 # We don't pass sub-options in, so no need to pass ui instance.
2219 2226 return path(None, None, rawloc=name)
2220 2227 except ValueError:
2221 2228 raise error.RepoError(_(b'repository %s does not exist') % name)
2222 2229
2223 2230
2224 2231 _pathsuboptions = {}
2225 2232
2226 2233
2227 2234 def pathsuboption(option, attr):
2228 2235 """Decorator used to declare a path sub-option.
2229 2236
2230 2237 Arguments are the sub-option name and the attribute it should set on
2231 2238 ``path`` instances.
2232 2239
2233 2240 The decorated function will receive as arguments a ``ui`` instance,
2234 2241 ``path`` instance, and the string value of this option from the config.
2235 2242 The function should return the value that will be set on the ``path``
2236 2243 instance.
2237 2244
2238 2245 This decorator can be used to perform additional verification of
2239 2246 sub-options and to change the type of sub-options.
2240 2247 """
2241 2248
2242 2249 def register(func):
2243 2250 _pathsuboptions[option] = (attr, func)
2244 2251 return func
2245 2252
2246 2253 return register
2247 2254
2248 2255
2249 2256 @pathsuboption(b'pushurl', b'pushloc')
2250 2257 def pushurlpathoption(ui, path, value):
2251 2258 u = util.url(value)
2252 2259 # Actually require a URL.
2253 2260 if not u.scheme:
2254 2261 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
2255 2262 return None
2256 2263
2257 2264 # Don't support the #foo syntax in the push URL to declare branch to
2258 2265 # push.
2259 2266 if u.fragment:
2260 2267 ui.warn(
2261 2268 _(
2262 2269 b'("#fragment" in paths.%s:pushurl not supported; '
2263 2270 b'ignoring)\n'
2264 2271 )
2265 2272 % path.name
2266 2273 )
2267 2274 u.fragment = None
2268 2275
2269 2276 return bytes(u)
2270 2277
2271 2278
2272 2279 @pathsuboption(b'pushrev', b'pushrev')
2273 2280 def pushrevpathoption(ui, path, value):
2274 2281 return value
2275 2282
2276 2283
2277 2284 class path(object):
2278 2285 """Represents an individual path and its configuration."""
2279 2286
2280 2287 def __init__(self, ui, name, rawloc=None, suboptions=None):
2281 2288 """Construct a path from its config options.
2282 2289
2283 2290 ``ui`` is the ``ui`` instance the path is coming from.
2284 2291 ``name`` is the symbolic name of the path.
2285 2292 ``rawloc`` is the raw location, as defined in the config.
2286 2293 ``pushloc`` is the raw locations pushes should be made to.
2287 2294
2288 2295 If ``name`` is not defined, we require that the location be a) a local
2289 2296 filesystem path with a .hg directory or b) a URL. If not,
2290 2297 ``ValueError`` is raised.
2291 2298 """
2292 2299 if not rawloc:
2293 2300 raise ValueError(b'rawloc must be defined')
2294 2301
2295 2302 # Locations may define branches via syntax <base>#<branch>.
2296 2303 u = util.url(rawloc)
2297 2304 branch = None
2298 2305 if u.fragment:
2299 2306 branch = u.fragment
2300 2307 u.fragment = None
2301 2308
2302 2309 self.url = u
2303 2310 self.branch = branch
2304 2311
2305 2312 self.name = name
2306 2313 self.rawloc = rawloc
2307 2314 self.loc = b'%s' % u
2308 2315
2309 2316 # When given a raw location but not a symbolic name, validate the
2310 2317 # location is valid.
2311 2318 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2312 2319 raise ValueError(
2313 2320 b'location is not a URL or path to a local '
2314 2321 b'repo: %s' % rawloc
2315 2322 )
2316 2323
2317 2324 suboptions = suboptions or {}
2318 2325
2319 2326 # Now process the sub-options. If a sub-option is registered, its
2320 2327 # attribute will always be present. The value will be None if there
2321 2328 # was no valid sub-option.
2322 2329 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
2323 2330 if suboption not in suboptions:
2324 2331 setattr(self, attr, None)
2325 2332 continue
2326 2333
2327 2334 value = func(ui, self, suboptions[suboption])
2328 2335 setattr(self, attr, value)
2329 2336
2330 2337 def _isvalidlocalpath(self, path):
2331 2338 """Returns True if the given path is a potentially valid repository.
2332 2339 This is its own function so that extensions can change the definition of
2333 2340 'valid' in this case (like when pulling from a git repo into a hg
2334 2341 one)."""
2335 2342 try:
2336 2343 return os.path.isdir(os.path.join(path, b'.hg'))
2337 2344 # Python 2 may return TypeError. Python 3, ValueError.
2338 2345 except (TypeError, ValueError):
2339 2346 return False
2340 2347
2341 2348 @property
2342 2349 def suboptions(self):
2343 2350 """Return sub-options and their values for this path.
2344 2351
2345 2352 This is intended to be used for presentation purposes.
2346 2353 """
2347 2354 d = {}
2348 2355 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
2349 2356 value = getattr(self, attr)
2350 2357 if value is not None:
2351 2358 d[subopt] = value
2352 2359 return d
2353 2360
2354 2361
2355 2362 # we instantiate one globally shared progress bar to avoid
2356 2363 # competing progress bars when multiple UI objects get created
2357 2364 _progresssingleton = None
2358 2365
2359 2366
2360 2367 def getprogbar(ui):
2361 2368 global _progresssingleton
2362 2369 if _progresssingleton is None:
2363 2370 # passing 'ui' object to the singleton is fishy,
2364 2371 # this is how the extension used to work but feel free to rework it.
2365 2372 _progresssingleton = progress.progbar(ui)
2366 2373 return _progresssingleton
2367 2374
2368 2375
2369 2376 def haveprogbar():
2370 2377 return _progresssingleton is not None
2371 2378
2372 2379
2373 2380 def _selectmsgdests(ui):
2374 2381 name = ui.config(b'ui', b'message-output')
2375 2382 if name == b'channel':
2376 2383 if ui.fmsg:
2377 2384 return ui.fmsg, ui.fmsg
2378 2385 else:
2379 2386 # fall back to ferr if channel isn't ready so that status/error
2380 2387 # messages can be printed
2381 2388 return ui.ferr, ui.ferr
2382 2389 if name == b'stdio':
2383 2390 return ui.fout, ui.ferr
2384 2391 if name == b'stderr':
2385 2392 return ui.ferr, ui.ferr
2386 2393 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2387 2394
2388 2395
2389 2396 def _writemsgwith(write, dest, *args, **opts):
2390 2397 """Write ui message with the given ui._write*() function
2391 2398
2392 2399 The specified message type is translated to 'ui.<type>' label if the dest
2393 2400 isn't a structured channel, so that the message will be colorized.
2394 2401 """
2395 2402 # TODO: maybe change 'type' to a mandatory option
2396 2403 if 'type' in opts and not getattr(dest, 'structured', False):
2397 2404 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2398 2405 write(dest, *args, **opts)
@@ -1,507 +1,501 b''
1 1 hide outer repo
2 2 $ hg init
3 3
4 4 Invalid syntax: no value
5 5
6 6 $ cat > .hg/hgrc << EOF
7 7 > novaluekey
8 8 > EOF
9 9 $ hg showconfig
10 10 config error at $TESTTMP/.hg/hgrc:1: novaluekey
11 11 [30]
12 12
13 13 Invalid syntax: no key
14 14
15 15 $ cat > .hg/hgrc << EOF
16 16 > =nokeyvalue
17 17 > EOF
18 18 $ hg showconfig
19 19 config error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
20 20 [30]
21 21
22 22 Test hint about invalid syntax from leading white space
23 23
24 24 $ cat > .hg/hgrc << EOF
25 25 > key=value
26 26 > EOF
27 27 $ hg showconfig
28 28 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: key=value
29 29 [30]
30 30
31 31 $ cat > .hg/hgrc << EOF
32 32 > [section]
33 33 > key=value
34 34 > EOF
35 35 $ hg showconfig
36 36 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: [section]
37 37 [30]
38 38
39 39 Reset hgrc
40 40
41 41 $ echo > .hg/hgrc
42 42
43 43 Test case sensitive configuration
44 44
45 45 $ cat <<EOF >> $HGRCPATH
46 46 > [Section]
47 47 > KeY = Case Sensitive
48 48 > key = lower case
49 49 > EOF
50 50
51 51 $ hg showconfig Section
52 52 Section.KeY=Case Sensitive
53 53 Section.key=lower case
54 54
55 55 $ hg showconfig Section -Tjson
56 56 [
57 57 {
58 58 "defaultvalue": null,
59 59 "name": "Section.KeY",
60 60 "source": "*.hgrc:*", (glob)
61 61 "value": "Case Sensitive"
62 62 },
63 63 {
64 64 "defaultvalue": null,
65 65 "name": "Section.key",
66 66 "source": "*.hgrc:*", (glob)
67 67 "value": "lower case"
68 68 }
69 69 ]
70 70 $ hg showconfig Section.KeY -Tjson
71 71 [
72 72 {
73 73 "defaultvalue": null,
74 74 "name": "Section.KeY",
75 75 "source": "*.hgrc:*", (glob)
76 76 "value": "Case Sensitive"
77 77 }
78 78 ]
79 79 $ hg showconfig -Tjson | tail -7
80 80 {
81 81 "defaultvalue": null,
82 82 "name": "*", (glob)
83 83 "source": "*", (glob)
84 84 "value": "*" (glob)
85 85 }
86 86 ]
87 87
88 88 Test config default of various types:
89 89
90 90 {"defaultvalue": ""} for -T'json(defaultvalue)' looks weird, but that's
91 91 how the templater works. Unknown keywords are evaluated to "".
92 92
93 93 dynamicdefault
94 94
95 95 $ hg config --config alias.foo= alias -Tjson
96 96 [
97 97 {
98 98 "name": "alias.foo",
99 99 "source": "--config",
100 100 "value": ""
101 101 }
102 102 ]
103 103 $ hg config --config alias.foo= alias -T'json(defaultvalue)'
104 104 [
105 105 {"defaultvalue": ""}
106 106 ]
107 107 $ hg config --config alias.foo= alias -T'{defaultvalue}\n'
108 108
109 109
110 110 null
111 111
112 112 $ hg config --config auth.cookiefile= auth -Tjson
113 113 [
114 114 {
115 115 "defaultvalue": null,
116 116 "name": "auth.cookiefile",
117 117 "source": "--config",
118 118 "value": ""
119 119 }
120 120 ]
121 121 $ hg config --config auth.cookiefile= auth -T'json(defaultvalue)'
122 122 [
123 123 {"defaultvalue": null}
124 124 ]
125 125 $ hg config --config auth.cookiefile= auth -T'{defaultvalue}\n'
126 126
127 127
128 128 false
129 129
130 130 $ hg config --config commands.commit.post-status= commands -Tjson
131 131 [
132 132 {
133 133 "defaultvalue": false,
134 134 "name": "commands.commit.post-status",
135 135 "source": "--config",
136 136 "value": ""
137 137 }
138 138 ]
139 139 $ hg config --config commands.commit.post-status= commands -T'json(defaultvalue)'
140 140 [
141 141 {"defaultvalue": false}
142 142 ]
143 143 $ hg config --config commands.commit.post-status= commands -T'{defaultvalue}\n'
144 144 False
145 145
146 146 true
147 147
148 148 $ hg config --config format.dotencode= format -Tjson
149 149 [
150 150 {
151 151 "defaultvalue": true,
152 152 "name": "format.dotencode",
153 153 "source": "--config",
154 154 "value": ""
155 155 }
156 156 ]
157 157 $ hg config --config format.dotencode= format -T'json(defaultvalue)'
158 158 [
159 159 {"defaultvalue": true}
160 160 ]
161 161 $ hg config --config format.dotencode= format -T'{defaultvalue}\n'
162 162 True
163 163
164 164 bytes
165 165
166 166 $ hg config --config commands.resolve.mark-check= commands -Tjson
167 167 [
168 168 {
169 169 "defaultvalue": "none",
170 170 "name": "commands.resolve.mark-check",
171 171 "source": "--config",
172 172 "value": ""
173 173 }
174 174 ]
175 175 $ hg config --config commands.resolve.mark-check= commands -T'json(defaultvalue)'
176 176 [
177 177 {"defaultvalue": "none"}
178 178 ]
179 179 $ hg config --config commands.resolve.mark-check= commands -T'{defaultvalue}\n'
180 180 none
181 181
182 182 empty list
183 183
184 184 $ hg config --config commands.show.aliasprefix= commands -Tjson
185 185 [
186 186 {
187 187 "defaultvalue": [],
188 188 "name": "commands.show.aliasprefix",
189 189 "source": "--config",
190 190 "value": ""
191 191 }
192 192 ]
193 193 $ hg config --config commands.show.aliasprefix= commands -T'json(defaultvalue)'
194 194 [
195 195 {"defaultvalue": []}
196 196 ]
197 197 $ hg config --config commands.show.aliasprefix= commands -T'{defaultvalue}\n'
198 198
199 199
200 200 nonempty list
201 201
202 202 $ hg config --config progress.format= progress -Tjson
203 203 [
204 204 {
205 205 "defaultvalue": ["topic", "bar", "number", "estimate"],
206 206 "name": "progress.format",
207 207 "source": "--config",
208 208 "value": ""
209 209 }
210 210 ]
211 211 $ hg config --config progress.format= progress -T'json(defaultvalue)'
212 212 [
213 213 {"defaultvalue": ["topic", "bar", "number", "estimate"]}
214 214 ]
215 215 $ hg config --config progress.format= progress -T'{defaultvalue}\n'
216 216 topic bar number estimate
217 217
218 218 int
219 219
220 220 $ hg config --config profiling.freq= profiling -Tjson
221 221 [
222 222 {
223 223 "defaultvalue": 1000,
224 224 "name": "profiling.freq",
225 225 "source": "--config",
226 226 "value": ""
227 227 }
228 228 ]
229 229 $ hg config --config profiling.freq= profiling -T'json(defaultvalue)'
230 230 [
231 231 {"defaultvalue": 1000}
232 232 ]
233 233 $ hg config --config profiling.freq= profiling -T'{defaultvalue}\n'
234 234 1000
235 235
236 236 float
237 237
238 238 $ hg config --config profiling.showmax= profiling -Tjson
239 239 [
240 240 {
241 241 "defaultvalue": 0.999,
242 242 "name": "profiling.showmax",
243 243 "source": "--config",
244 244 "value": ""
245 245 }
246 246 ]
247 247 $ hg config --config profiling.showmax= profiling -T'json(defaultvalue)'
248 248 [
249 249 {"defaultvalue": 0.999}
250 250 ]
251 251 $ hg config --config profiling.showmax= profiling -T'{defaultvalue}\n'
252 252 0.999
253 253
254 254 Test empty config source:
255 255
256 256 $ cat <<EOF > emptysource.py
257 257 > def reposetup(ui, repo):
258 258 > ui.setconfig(b'empty', b'source', b'value')
259 259 > EOF
260 260 $ cp .hg/hgrc .hg/hgrc.orig
261 261 $ cat <<EOF >> .hg/hgrc
262 262 > [extensions]
263 263 > emptysource = `pwd`/emptysource.py
264 264 > EOF
265 265
266 266 $ hg config --debug empty.source
267 267 read config from: * (glob)
268 268 none: value
269 269 $ hg config empty.source -Tjson
270 270 [
271 271 {
272 272 "defaultvalue": null,
273 273 "name": "empty.source",
274 274 "source": "",
275 275 "value": "value"
276 276 }
277 277 ]
278 278
279 279 $ cp .hg/hgrc.orig .hg/hgrc
280 280
281 281 Test "%unset"
282 282
283 283 $ cat >> $HGRCPATH <<EOF
284 284 > [unsettest]
285 285 > local-hgrcpath = should be unset (HGRCPATH)
286 286 > %unset local-hgrcpath
287 287 >
288 288 > global = should be unset (HGRCPATH)
289 289 >
290 290 > both = should be unset (HGRCPATH)
291 291 >
292 292 > set-after-unset = should be unset (HGRCPATH)
293 293 > EOF
294 294
295 295 $ cat >> .hg/hgrc <<EOF
296 296 > [unsettest]
297 297 > local-hgrc = should be unset (.hg/hgrc)
298 298 > %unset local-hgrc
299 299 >
300 300 > %unset global
301 301 >
302 302 > both = should be unset (.hg/hgrc)
303 303 > %unset both
304 304 >
305 305 > set-after-unset = should be unset (.hg/hgrc)
306 306 > %unset set-after-unset
307 307 > set-after-unset = should be set (.hg/hgrc)
308 308 > EOF
309 309
310 310 $ hg showconfig unsettest
311 311 unsettest.set-after-unset=should be set (.hg/hgrc)
312 312
313 313 Test exit code when no config matches
314 314
315 315 $ hg config Section.idontexist
316 316 [1]
317 317
318 318 sub-options in [paths] aren't expanded
319 319
320 320 $ cat > .hg/hgrc << EOF
321 321 > [paths]
322 322 > foo = ~/foo
323 323 > foo:suboption = ~/foo
324 324 > EOF
325 325
326 326 $ hg showconfig paths
327 327 paths.foo:suboption=~/foo
328 328 paths.foo=$TESTTMP/foo
329 329
330 330 edit failure
331 331
332 332 $ HGEDITOR=false hg config --edit
333 333 abort: edit failed: false exited with status 1
334 334 [10]
335 335
336 336 config affected by environment variables
337 337
338 338 $ EDITOR=e1 VISUAL=e2 hg config --debug | grep 'ui\.editor'
339 339 $VISUAL: ui.editor=e2
340 340
341 341 $ VISUAL=e2 hg config --debug --config ui.editor=e3 | grep 'ui\.editor'
342 342 --config: ui.editor=e3
343 343
344 344 $ PAGER=p1 hg config --debug | grep 'pager\.pager'
345 345 $PAGER: pager.pager=p1
346 346
347 347 $ PAGER=p1 hg config --debug --config pager.pager=p2 | grep 'pager\.pager'
348 348 --config: pager.pager=p2
349 349
350 350 verify that aliases are evaluated as well
351 351
352 352 $ hg init aliastest
353 353 $ cd aliastest
354 354 $ cat > .hg/hgrc << EOF
355 355 > [ui]
356 356 > user = repo user
357 357 > EOF
358 358 $ touch index
359 359 $ unset HGUSER
360 360 $ hg ci -Am test
361 361 adding index
362 362 $ hg log --template '{author}\n'
363 363 repo user
364 364 $ cd ..
365 365
366 366 alias has lower priority
367 367
368 368 $ hg init aliaspriority
369 369 $ cd aliaspriority
370 370 $ cat > .hg/hgrc << EOF
371 371 > [ui]
372 372 > user = alias user
373 373 > username = repo user
374 374 > EOF
375 375 $ touch index
376 376 $ unset HGUSER
377 377 $ hg ci -Am test
378 378 adding index
379 379 $ hg log --template '{author}\n'
380 380 repo user
381 381 $ cd ..
382 382
383 383 configs should be read in lexicographical order
384 384
385 385 $ mkdir configs
386 386 $ for i in `$TESTDIR/seq.py 10 99`; do
387 387 > printf "[section]\nkey=$i" > configs/$i.rc
388 388 > done
389 389 $ HGRCPATH=configs hg config section.key
390 390 99
391 391
392 392 Configuration priority
393 393 ======================
394 394
395 395 setup necessary file
396 396
397 397 $ cat > file-A.rc << EOF
398 398 > [config-test]
399 399 > basic = value-A
400 400 > pre-include= value-A
401 401 > %include ./included.rc
402 402 > post-include= value-A
403 403 > [command-templates]
404 404 > log = "value-A\n"
405 405 > EOF
406 406
407 407 $ cat > file-B.rc << EOF
408 408 > [config-test]
409 409 > basic = value-B
410 410 > [ui]
411 411 > logtemplate = "value-B\n"
412 412 > EOF
413 413
414 414
415 415 $ cat > included.rc << EOF
416 416 > [config-test]
417 417 > pre-include= value-included
418 418 > post-include= value-included
419 419 > EOF
420 420
421 421 $ cat > file-C.rc << EOF
422 422 > %include ./included-alias-C.rc
423 423 > [ui]
424 424 > logtemplate = "value-C\n"
425 425 > EOF
426 426
427 427 $ cat > included-alias-C.rc << EOF
428 428 > [command-templates]
429 429 > log = "value-included\n"
430 430 > EOF
431 431
432 432
433 433 $ cat > file-D.rc << EOF
434 434 > [command-templates]
435 435 > log = "value-D\n"
436 436 > %include ./included-alias-D.rc
437 437 > EOF
438 438
439 439 $ cat > included-alias-D.rc << EOF
440 440 > [ui]
441 441 > logtemplate = "value-included\n"
442 442 > EOF
443 443
444 444 Simple order checking
445 445 ---------------------
446 446
447 447 If file B is read after file A, value from B overwrite value from A.
448 448
449 449 $ HGRCPATH="file-A.rc:file-B.rc" hg config config-test.basic
450 450 value-B
451 451
452 452 Ordering from include
453 453 ---------------------
454 454
455 455 value from an include overwrite value defined before the include, but not the one defined after the include
456 456
457 457 $ HGRCPATH="file-A.rc" hg config config-test.pre-include
458 458 value-included
459 459 $ HGRCPATH="file-A.rc" hg config config-test.post-include
460 460 value-A
461 461
462 462 command line override
463 463 ---------------------
464 464
465 465 $ HGRCPATH="file-A.rc:file-B.rc" hg config config-test.basic --config config-test.basic=value-CLI
466 466 value-CLI
467 467
468 468 Alias ordering
469 469 --------------
470 470
471 471 The official config is now `command-templates.log`, the historical
472 472 `ui.logtemplate` is a valid alternative for it.
473 473
474 474 When both are defined, The config value read the last "win", this should keep
475 475 being true if the config have other alias. In other word, the config value read
476 476 earlier will be considered "lower level" and the config read later would be
477 477 considered "higher level". And higher level values wins.
478 478
479 BROKEN: currently not the case.
480
481 479 $ HGRCPATH="file-A.rc" hg log -r .
482 480 value-A
483 481 $ HGRCPATH="file-B.rc" hg log -r .
484 482 value-B
485 483 $ HGRCPATH="file-A.rc:file-B.rc" hg log -r .
486 value-A (known-bad-output !)
487 value-B (missing-correct-output !)
484 value-B
488 485
489 486 Alias and include
490 487 -----------------
491 488
492 489 The pre/post include priority should also apply when tie-breaking alternatives.
493 490 See the case above for details about the two config options used.
494 491
495 492 $ HGRCPATH="file-C.rc" hg log -r .
496 value-included (known-bad-output !)
497 value-C (missing-correct-output !)
493 value-C
498 494 $ HGRCPATH="file-D.rc" hg log -r .
499 value-D (known-bad-output !)
500 value-included (missing-correct-output !)
495 value-included
501 496
502 497 command line override
503 498 ---------------------
504 499
505 500 $ HGRCPATH="file-A.rc:file-B.rc" hg log -r . --config ui.logtemplate="value-CLI\n"
506 value-A (known-bad-output !)
507 value-CLI (missing-correct-output !)
501 value-CLI
General Comments 0
You need to be logged in to leave comments. Login now