##// END OF EJS Templates
ui: remove commands.status.terse=u from ui.tweakdefaults...
Gregory Szorc -
r38785:fe3ca1e6 @95 stable
parent child Browse files
Show More
@@ -1,1908 +1,1906 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import traceback
22 22
23 23 from .i18n import _
24 24 from .node import hex
25 25
26 26 from . import (
27 27 color,
28 28 config,
29 29 configitems,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 progress,
34 34 pycompat,
35 35 rcutil,
36 36 scmutil,
37 37 util,
38 38 )
39 39 from .utils import (
40 40 dateutil,
41 41 procutil,
42 42 stringutil,
43 43 )
44 44
45 45 urlreq = util.urlreq
46 46
47 47 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
48 48 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
49 49 if not c.isalnum())
50 50
51 51 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
52 52 tweakrc = b"""
53 53 [ui]
54 54 # The rollback command is dangerous. As a rule, don't use it.
55 55 rollback = False
56 56 # Make `hg status` report copy information
57 57 statuscopies = yes
58 58 # Prefer curses UIs when available. Revert to plain-text with `text`.
59 59 interface = curses
60 60
61 61 [commands]
62 62 # Grep working directory by default.
63 63 grep.all-files = True
64 64 # Make `hg status` emit cwd-relative paths by default.
65 65 status.relative = yes
66 66 # Refuse to perform an `hg update` that would cause a file content merge
67 67 update.check = noconflict
68 68 # Show conflicts information in `hg status`
69 69 status.verbose = True
70 # Collapse entire directories that contain only unknown files
71 status.terse = u
72 70
73 71 [diff]
74 72 git = 1
75 73 showfunc = 1
76 74 word-diff = 1
77 75 """
78 76
79 77 samplehgrcs = {
80 78 'user':
81 79 b"""# example user config (see 'hg help config' for more info)
82 80 [ui]
83 81 # name and email, e.g.
84 82 # username = Jane Doe <jdoe@example.com>
85 83 username =
86 84
87 85 # We recommend enabling tweakdefaults to get slight improvements to
88 86 # the UI over time. Make sure to set HGPLAIN in the environment when
89 87 # writing scripts!
90 88 # tweakdefaults = True
91 89
92 90 # uncomment to disable color in command output
93 91 # (see 'hg help color' for details)
94 92 # color = never
95 93
96 94 # uncomment to disable command output pagination
97 95 # (see 'hg help pager' for details)
98 96 # paginate = never
99 97
100 98 [extensions]
101 99 # uncomment these lines to enable some popular extensions
102 100 # (see 'hg help extensions' for more info)
103 101 #
104 102 # churn =
105 103 """,
106 104
107 105 'cloned':
108 106 b"""# example repository config (see 'hg help config' for more info)
109 107 [paths]
110 108 default = %s
111 109
112 110 # path aliases to other clones of this repo in URLs or filesystem paths
113 111 # (see 'hg help config.paths' for more info)
114 112 #
115 113 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
116 114 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
117 115 # my-clone = /home/jdoe/jdoes-clone
118 116
119 117 [ui]
120 118 # name and email (local to this repository, optional), e.g.
121 119 # username = Jane Doe <jdoe@example.com>
122 120 """,
123 121
124 122 'local':
125 123 b"""# example repository config (see 'hg help config' for more info)
126 124 [paths]
127 125 # path aliases to other clones of this repo in URLs or filesystem paths
128 126 # (see 'hg help config.paths' for more info)
129 127 #
130 128 # default = http://example.com/hg/example-repo
131 129 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
132 130 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
133 131 # my-clone = /home/jdoe/jdoes-clone
134 132
135 133 [ui]
136 134 # name and email (local to this repository, optional), e.g.
137 135 # username = Jane Doe <jdoe@example.com>
138 136 """,
139 137
140 138 'global':
141 139 b"""# example system-wide hg config (see 'hg help config' for more info)
142 140
143 141 [ui]
144 142 # uncomment to disable color in command output
145 143 # (see 'hg help color' for details)
146 144 # color = never
147 145
148 146 # uncomment to disable command output pagination
149 147 # (see 'hg help pager' for details)
150 148 # paginate = never
151 149
152 150 [extensions]
153 151 # uncomment these lines to enable some popular extensions
154 152 # (see 'hg help extensions' for more info)
155 153 #
156 154 # blackbox =
157 155 # churn =
158 156 """,
159 157 }
160 158
161 159 def _maybestrurl(maybebytes):
162 160 return pycompat.rapply(pycompat.strurl, maybebytes)
163 161
164 162 def _maybebytesurl(maybestr):
165 163 return pycompat.rapply(pycompat.bytesurl, maybestr)
166 164
167 165 class httppasswordmgrdbproxy(object):
168 166 """Delays loading urllib2 until it's needed."""
169 167 def __init__(self):
170 168 self._mgr = None
171 169
172 170 def _get_mgr(self):
173 171 if self._mgr is None:
174 172 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
175 173 return self._mgr
176 174
177 175 def add_password(self, realm, uris, user, passwd):
178 176 return self._get_mgr().add_password(
179 177 _maybestrurl(realm), _maybestrurl(uris),
180 178 _maybestrurl(user), _maybestrurl(passwd))
181 179
182 180 def find_user_password(self, realm, uri):
183 181 mgr = self._get_mgr()
184 182 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
185 183 _maybestrurl(uri)))
186 184
187 185 def _catchterm(*args):
188 186 raise error.SignalInterrupt
189 187
190 188 # unique object used to detect no default value has been provided when
191 189 # retrieving configuration value.
192 190 _unset = object()
193 191
194 192 # _reqexithandlers: callbacks run at the end of a request
195 193 _reqexithandlers = []
196 194
197 195 class ui(object):
198 196 def __init__(self, src=None):
199 197 """Create a fresh new ui object if no src given
200 198
201 199 Use uimod.ui.load() to create a ui which knows global and user configs.
202 200 In most cases, you should use ui.copy() to create a copy of an existing
203 201 ui object.
204 202 """
205 203 # _buffers: used for temporary capture of output
206 204 self._buffers = []
207 205 # 3-tuple describing how each buffer in the stack behaves.
208 206 # Values are (capture stderr, capture subprocesses, apply labels).
209 207 self._bufferstates = []
210 208 # When a buffer is active, defines whether we are expanding labels.
211 209 # This exists to prevent an extra list lookup.
212 210 self._bufferapplylabels = None
213 211 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
214 212 self._reportuntrusted = True
215 213 self._knownconfig = configitems.coreitems
216 214 self._ocfg = config.config() # overlay
217 215 self._tcfg = config.config() # trusted
218 216 self._ucfg = config.config() # untrusted
219 217 self._trustusers = set()
220 218 self._trustgroups = set()
221 219 self.callhooks = True
222 220 # Insecure server connections requested.
223 221 self.insecureconnections = False
224 222 # Blocked time
225 223 self.logblockedtimes = False
226 224 # color mode: see mercurial/color.py for possible value
227 225 self._colormode = None
228 226 self._terminfoparams = {}
229 227 self._styles = {}
230 228 self._uninterruptible = False
231 229
232 230 if src:
233 231 self.fout = src.fout
234 232 self.ferr = src.ferr
235 233 self.fin = src.fin
236 234 self.pageractive = src.pageractive
237 235 self._disablepager = src._disablepager
238 236 self._tweaked = src._tweaked
239 237
240 238 self._tcfg = src._tcfg.copy()
241 239 self._ucfg = src._ucfg.copy()
242 240 self._ocfg = src._ocfg.copy()
243 241 self._trustusers = src._trustusers.copy()
244 242 self._trustgroups = src._trustgroups.copy()
245 243 self.environ = src.environ
246 244 self.callhooks = src.callhooks
247 245 self.insecureconnections = src.insecureconnections
248 246 self._colormode = src._colormode
249 247 self._terminfoparams = src._terminfoparams.copy()
250 248 self._styles = src._styles.copy()
251 249
252 250 self.fixconfig()
253 251
254 252 self.httppasswordmgrdb = src.httppasswordmgrdb
255 253 self._blockedtimes = src._blockedtimes
256 254 else:
257 255 self.fout = procutil.stdout
258 256 self.ferr = procutil.stderr
259 257 self.fin = procutil.stdin
260 258 self.pageractive = False
261 259 self._disablepager = False
262 260 self._tweaked = False
263 261
264 262 # shared read-only environment
265 263 self.environ = encoding.environ
266 264
267 265 self.httppasswordmgrdb = httppasswordmgrdbproxy()
268 266 self._blockedtimes = collections.defaultdict(int)
269 267
270 268 allowed = self.configlist('experimental', 'exportableenviron')
271 269 if '*' in allowed:
272 270 self._exportableenviron = self.environ
273 271 else:
274 272 self._exportableenviron = {}
275 273 for k in allowed:
276 274 if k in self.environ:
277 275 self._exportableenviron[k] = self.environ[k]
278 276
279 277 @classmethod
280 278 def load(cls):
281 279 """Create a ui and load global and user configs"""
282 280 u = cls()
283 281 # we always trust global config files and environment variables
284 282 for t, f in rcutil.rccomponents():
285 283 if t == 'path':
286 284 u.readconfig(f, trust=True)
287 285 elif t == 'items':
288 286 sections = set()
289 287 for section, name, value, source in f:
290 288 # do not set u._ocfg
291 289 # XXX clean this up once immutable config object is a thing
292 290 u._tcfg.set(section, name, value, source)
293 291 u._ucfg.set(section, name, value, source)
294 292 sections.add(section)
295 293 for section in sections:
296 294 u.fixconfig(section=section)
297 295 else:
298 296 raise error.ProgrammingError('unknown rctype: %s' % t)
299 297 u._maybetweakdefaults()
300 298 return u
301 299
302 300 def _maybetweakdefaults(self):
303 301 if not self.configbool('ui', 'tweakdefaults'):
304 302 return
305 303 if self._tweaked or self.plain('tweakdefaults'):
306 304 return
307 305
308 306 # Note: it is SUPER IMPORTANT that you set self._tweaked to
309 307 # True *before* any calls to setconfig(), otherwise you'll get
310 308 # infinite recursion between setconfig and this method.
311 309 #
312 310 # TODO: We should extract an inner method in setconfig() to
313 311 # avoid this weirdness.
314 312 self._tweaked = True
315 313 tmpcfg = config.config()
316 314 tmpcfg.parse('<tweakdefaults>', tweakrc)
317 315 for section in tmpcfg:
318 316 for name, value in tmpcfg.items(section):
319 317 if not self.hasconfig(section, name):
320 318 self.setconfig(section, name, value, "<tweakdefaults>")
321 319
322 320 def copy(self):
323 321 return self.__class__(self)
324 322
325 323 def resetstate(self):
326 324 """Clear internal state that shouldn't persist across commands"""
327 325 if self._progbar:
328 326 self._progbar.resetstate() # reset last-print time of progress bar
329 327 self.httppasswordmgrdb = httppasswordmgrdbproxy()
330 328
331 329 @contextlib.contextmanager
332 330 def timeblockedsection(self, key):
333 331 # this is open-coded below - search for timeblockedsection to find them
334 332 starttime = util.timer()
335 333 try:
336 334 yield
337 335 finally:
338 336 self._blockedtimes[key + '_blocked'] += \
339 337 (util.timer() - starttime) * 1000
340 338
341 339 @contextlib.contextmanager
342 340 def uninterruptable(self):
343 341 """Mark an operation as unsafe.
344 342
345 343 Most operations on a repository are safe to interrupt, but a
346 344 few are risky (for example repair.strip). This context manager
347 345 lets you advise Mercurial that something risky is happening so
348 346 that control-C etc can be blocked if desired.
349 347 """
350 348 enabled = self.configbool('experimental', 'nointerrupt')
351 349 if (enabled and
352 350 self.configbool('experimental', 'nointerrupt-interactiveonly')):
353 351 enabled = self.interactive()
354 352 if self._uninterruptible or not enabled:
355 353 # if nointerrupt support is turned off, the process isn't
356 354 # interactive, or we're already in an uninterruptable
357 355 # block, do nothing.
358 356 yield
359 357 return
360 358 def warn():
361 359 self.warn(_("shutting down cleanly\n"))
362 360 self.warn(
363 361 _("press ^C again to terminate immediately (dangerous)\n"))
364 362 return True
365 363 with procutil.uninterruptable(warn):
366 364 try:
367 365 self._uninterruptible = True
368 366 yield
369 367 finally:
370 368 self._uninterruptible = False
371 369
372 370 def formatter(self, topic, opts):
373 371 return formatter.formatter(self, self, topic, opts)
374 372
375 373 def _trusted(self, fp, f):
376 374 st = util.fstat(fp)
377 375 if util.isowner(st):
378 376 return True
379 377
380 378 tusers, tgroups = self._trustusers, self._trustgroups
381 379 if '*' in tusers or '*' in tgroups:
382 380 return True
383 381
384 382 user = util.username(st.st_uid)
385 383 group = util.groupname(st.st_gid)
386 384 if user in tusers or group in tgroups or user == util.username():
387 385 return True
388 386
389 387 if self._reportuntrusted:
390 388 self.warn(_('not trusting file %s from untrusted '
391 389 'user %s, group %s\n') % (f, user, group))
392 390 return False
393 391
394 392 def readconfig(self, filename, root=None, trust=False,
395 393 sections=None, remap=None):
396 394 try:
397 395 fp = open(filename, u'rb')
398 396 except IOError:
399 397 if not sections: # ignore unless we were looking for something
400 398 return
401 399 raise
402 400
403 401 cfg = config.config()
404 402 trusted = sections or trust or self._trusted(fp, filename)
405 403
406 404 try:
407 405 cfg.read(filename, fp, sections=sections, remap=remap)
408 406 fp.close()
409 407 except error.ConfigError as inst:
410 408 if trusted:
411 409 raise
412 410 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
413 411
414 412 if self.plain():
415 413 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
416 414 'logtemplate', 'statuscopies', 'style',
417 415 'traceback', 'verbose'):
418 416 if k in cfg['ui']:
419 417 del cfg['ui'][k]
420 418 for k, v in cfg.items('defaults'):
421 419 del cfg['defaults'][k]
422 420 for k, v in cfg.items('commands'):
423 421 del cfg['commands'][k]
424 422 # Don't remove aliases from the configuration if in the exceptionlist
425 423 if self.plain('alias'):
426 424 for k, v in cfg.items('alias'):
427 425 del cfg['alias'][k]
428 426 if self.plain('revsetalias'):
429 427 for k, v in cfg.items('revsetalias'):
430 428 del cfg['revsetalias'][k]
431 429 if self.plain('templatealias'):
432 430 for k, v in cfg.items('templatealias'):
433 431 del cfg['templatealias'][k]
434 432
435 433 if trusted:
436 434 self._tcfg.update(cfg)
437 435 self._tcfg.update(self._ocfg)
438 436 self._ucfg.update(cfg)
439 437 self._ucfg.update(self._ocfg)
440 438
441 439 if root is None:
442 440 root = os.path.expanduser('~')
443 441 self.fixconfig(root=root)
444 442
445 443 def fixconfig(self, root=None, section=None):
446 444 if section in (None, 'paths'):
447 445 # expand vars and ~
448 446 # translate paths relative to root (or home) into absolute paths
449 447 root = root or pycompat.getcwd()
450 448 for c in self._tcfg, self._ucfg, self._ocfg:
451 449 for n, p in c.items('paths'):
452 450 # Ignore sub-options.
453 451 if ':' in n:
454 452 continue
455 453 if not p:
456 454 continue
457 455 if '%%' in p:
458 456 s = self.configsource('paths', n) or 'none'
459 457 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
460 458 % (n, p, s))
461 459 p = p.replace('%%', '%')
462 460 p = util.expandpath(p)
463 461 if not util.hasscheme(p) and not os.path.isabs(p):
464 462 p = os.path.normpath(os.path.join(root, p))
465 463 c.set("paths", n, p)
466 464
467 465 if section in (None, 'ui'):
468 466 # update ui options
469 467 self.debugflag = self.configbool('ui', 'debug')
470 468 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
471 469 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
472 470 if self.verbose and self.quiet:
473 471 self.quiet = self.verbose = False
474 472 self._reportuntrusted = self.debugflag or self.configbool("ui",
475 473 "report_untrusted")
476 474 self.tracebackflag = self.configbool('ui', 'traceback')
477 475 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
478 476
479 477 if section in (None, 'trusted'):
480 478 # update trust information
481 479 self._trustusers.update(self.configlist('trusted', 'users'))
482 480 self._trustgroups.update(self.configlist('trusted', 'groups'))
483 481
484 482 def backupconfig(self, section, item):
485 483 return (self._ocfg.backup(section, item),
486 484 self._tcfg.backup(section, item),
487 485 self._ucfg.backup(section, item),)
488 486 def restoreconfig(self, data):
489 487 self._ocfg.restore(data[0])
490 488 self._tcfg.restore(data[1])
491 489 self._ucfg.restore(data[2])
492 490
493 491 def setconfig(self, section, name, value, source=''):
494 492 for cfg in (self._ocfg, self._tcfg, self._ucfg):
495 493 cfg.set(section, name, value, source)
496 494 self.fixconfig(section=section)
497 495 self._maybetweakdefaults()
498 496
499 497 def _data(self, untrusted):
500 498 return untrusted and self._ucfg or self._tcfg
501 499
502 500 def configsource(self, section, name, untrusted=False):
503 501 return self._data(untrusted).source(section, name)
504 502
505 503 def config(self, section, name, default=_unset, untrusted=False):
506 504 """return the plain string version of a config"""
507 505 value = self._config(section, name, default=default,
508 506 untrusted=untrusted)
509 507 if value is _unset:
510 508 return None
511 509 return value
512 510
513 511 def _config(self, section, name, default=_unset, untrusted=False):
514 512 value = itemdefault = default
515 513 item = self._knownconfig.get(section, {}).get(name)
516 514 alternates = [(section, name)]
517 515
518 516 if item is not None:
519 517 alternates.extend(item.alias)
520 518 if callable(item.default):
521 519 itemdefault = item.default()
522 520 else:
523 521 itemdefault = item.default
524 522 else:
525 523 msg = ("accessing unregistered config item: '%s.%s'")
526 524 msg %= (section, name)
527 525 self.develwarn(msg, 2, 'warn-config-unknown')
528 526
529 527 if default is _unset:
530 528 if item is None:
531 529 value = default
532 530 elif item.default is configitems.dynamicdefault:
533 531 value = None
534 532 msg = "config item requires an explicit default value: '%s.%s'"
535 533 msg %= (section, name)
536 534 self.develwarn(msg, 2, 'warn-config-default')
537 535 else:
538 536 value = itemdefault
539 537 elif (item is not None
540 538 and item.default is not configitems.dynamicdefault
541 539 and default != itemdefault):
542 540 msg = ("specifying a mismatched default value for a registered "
543 541 "config item: '%s.%s' '%s'")
544 542 msg %= (section, name, pycompat.bytestr(default))
545 543 self.develwarn(msg, 2, 'warn-config-default')
546 544
547 545 for s, n in alternates:
548 546 candidate = self._data(untrusted).get(s, n, None)
549 547 if candidate is not None:
550 548 value = candidate
551 549 section = s
552 550 name = n
553 551 break
554 552
555 553 if self.debugflag and not untrusted and self._reportuntrusted:
556 554 for s, n in alternates:
557 555 uvalue = self._ucfg.get(s, n)
558 556 if uvalue is not None and uvalue != value:
559 557 self.debug("ignoring untrusted configuration option "
560 558 "%s.%s = %s\n" % (s, n, uvalue))
561 559 return value
562 560
563 561 def configsuboptions(self, section, name, default=_unset, untrusted=False):
564 562 """Get a config option and all sub-options.
565 563
566 564 Some config options have sub-options that are declared with the
567 565 format "key:opt = value". This method is used to return the main
568 566 option and all its declared sub-options.
569 567
570 568 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
571 569 is a dict of defined sub-options where keys and values are strings.
572 570 """
573 571 main = self.config(section, name, default, untrusted=untrusted)
574 572 data = self._data(untrusted)
575 573 sub = {}
576 574 prefix = '%s:' % name
577 575 for k, v in data.items(section):
578 576 if k.startswith(prefix):
579 577 sub[k[len(prefix):]] = v
580 578
581 579 if self.debugflag and not untrusted and self._reportuntrusted:
582 580 for k, v in sub.items():
583 581 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
584 582 if uvalue is not None and uvalue != v:
585 583 self.debug('ignoring untrusted configuration option '
586 584 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
587 585
588 586 return main, sub
589 587
590 588 def configpath(self, section, name, default=_unset, untrusted=False):
591 589 'get a path config item, expanded relative to repo root or config file'
592 590 v = self.config(section, name, default, untrusted)
593 591 if v is None:
594 592 return None
595 593 if not os.path.isabs(v) or "://" not in v:
596 594 src = self.configsource(section, name, untrusted)
597 595 if ':' in src:
598 596 base = os.path.dirname(src.rsplit(':')[0])
599 597 v = os.path.join(base, os.path.expanduser(v))
600 598 return v
601 599
602 600 def configbool(self, section, name, default=_unset, untrusted=False):
603 601 """parse a configuration element as a boolean
604 602
605 603 >>> u = ui(); s = b'foo'
606 604 >>> u.setconfig(s, b'true', b'yes')
607 605 >>> u.configbool(s, b'true')
608 606 True
609 607 >>> u.setconfig(s, b'false', b'no')
610 608 >>> u.configbool(s, b'false')
611 609 False
612 610 >>> u.configbool(s, b'unknown')
613 611 False
614 612 >>> u.configbool(s, b'unknown', True)
615 613 True
616 614 >>> u.setconfig(s, b'invalid', b'somevalue')
617 615 >>> u.configbool(s, b'invalid')
618 616 Traceback (most recent call last):
619 617 ...
620 618 ConfigError: foo.invalid is not a boolean ('somevalue')
621 619 """
622 620
623 621 v = self._config(section, name, default, untrusted=untrusted)
624 622 if v is None:
625 623 return v
626 624 if v is _unset:
627 625 if default is _unset:
628 626 return False
629 627 return default
630 628 if isinstance(v, bool):
631 629 return v
632 630 b = stringutil.parsebool(v)
633 631 if b is None:
634 632 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
635 633 % (section, name, v))
636 634 return b
637 635
638 636 def configwith(self, convert, section, name, default=_unset,
639 637 desc=None, untrusted=False):
640 638 """parse a configuration element with a conversion function
641 639
642 640 >>> u = ui(); s = b'foo'
643 641 >>> u.setconfig(s, b'float1', b'42')
644 642 >>> u.configwith(float, s, b'float1')
645 643 42.0
646 644 >>> u.setconfig(s, b'float2', b'-4.25')
647 645 >>> u.configwith(float, s, b'float2')
648 646 -4.25
649 647 >>> u.configwith(float, s, b'unknown', 7)
650 648 7.0
651 649 >>> u.setconfig(s, b'invalid', b'somevalue')
652 650 >>> u.configwith(float, s, b'invalid')
653 651 Traceback (most recent call last):
654 652 ...
655 653 ConfigError: foo.invalid is not a valid float ('somevalue')
656 654 >>> u.configwith(float, s, b'invalid', desc=b'womble')
657 655 Traceback (most recent call last):
658 656 ...
659 657 ConfigError: foo.invalid is not a valid womble ('somevalue')
660 658 """
661 659
662 660 v = self.config(section, name, default, untrusted)
663 661 if v is None:
664 662 return v # do not attempt to convert None
665 663 try:
666 664 return convert(v)
667 665 except (ValueError, error.ParseError):
668 666 if desc is None:
669 667 desc = pycompat.sysbytes(convert.__name__)
670 668 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
671 669 % (section, name, desc, v))
672 670
673 671 def configint(self, section, name, default=_unset, untrusted=False):
674 672 """parse a configuration element as an integer
675 673
676 674 >>> u = ui(); s = b'foo'
677 675 >>> u.setconfig(s, b'int1', b'42')
678 676 >>> u.configint(s, b'int1')
679 677 42
680 678 >>> u.setconfig(s, b'int2', b'-42')
681 679 >>> u.configint(s, b'int2')
682 680 -42
683 681 >>> u.configint(s, b'unknown', 7)
684 682 7
685 683 >>> u.setconfig(s, b'invalid', b'somevalue')
686 684 >>> u.configint(s, b'invalid')
687 685 Traceback (most recent call last):
688 686 ...
689 687 ConfigError: foo.invalid is not a valid integer ('somevalue')
690 688 """
691 689
692 690 return self.configwith(int, section, name, default, 'integer',
693 691 untrusted)
694 692
695 693 def configbytes(self, section, name, default=_unset, untrusted=False):
696 694 """parse a configuration element as a quantity in bytes
697 695
698 696 Units can be specified as b (bytes), k or kb (kilobytes), m or
699 697 mb (megabytes), g or gb (gigabytes).
700 698
701 699 >>> u = ui(); s = b'foo'
702 700 >>> u.setconfig(s, b'val1', b'42')
703 701 >>> u.configbytes(s, b'val1')
704 702 42
705 703 >>> u.setconfig(s, b'val2', b'42.5 kb')
706 704 >>> u.configbytes(s, b'val2')
707 705 43520
708 706 >>> u.configbytes(s, b'unknown', b'7 MB')
709 707 7340032
710 708 >>> u.setconfig(s, b'invalid', b'somevalue')
711 709 >>> u.configbytes(s, b'invalid')
712 710 Traceback (most recent call last):
713 711 ...
714 712 ConfigError: foo.invalid is not a byte quantity ('somevalue')
715 713 """
716 714
717 715 value = self._config(section, name, default, untrusted)
718 716 if value is _unset:
719 717 if default is _unset:
720 718 default = 0
721 719 value = default
722 720 if not isinstance(value, bytes):
723 721 return value
724 722 try:
725 723 return util.sizetoint(value)
726 724 except error.ParseError:
727 725 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
728 726 % (section, name, value))
729 727
730 728 def configlist(self, section, name, default=_unset, untrusted=False):
731 729 """parse a configuration element as a list of comma/space separated
732 730 strings
733 731
734 732 >>> u = ui(); s = b'foo'
735 733 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
736 734 >>> u.configlist(s, b'list1')
737 735 ['this', 'is', 'a small', 'test']
738 736 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
739 737 >>> u.configlist(s, b'list2')
740 738 ['this', 'is', 'a small', 'test']
741 739 """
742 740 # default is not always a list
743 741 v = self.configwith(config.parselist, section, name, default,
744 742 'list', untrusted)
745 743 if isinstance(v, bytes):
746 744 return config.parselist(v)
747 745 elif v is None:
748 746 return []
749 747 return v
750 748
751 749 def configdate(self, section, name, default=_unset, untrusted=False):
752 750 """parse a configuration element as a tuple of ints
753 751
754 752 >>> u = ui(); s = b'foo'
755 753 >>> u.setconfig(s, b'date', b'0 0')
756 754 >>> u.configdate(s, b'date')
757 755 (0, 0)
758 756 """
759 757 if self.config(section, name, default, untrusted):
760 758 return self.configwith(dateutil.parsedate, section, name, default,
761 759 'date', untrusted)
762 760 if default is _unset:
763 761 return None
764 762 return default
765 763
766 764 def hasconfig(self, section, name, untrusted=False):
767 765 return self._data(untrusted).hasitem(section, name)
768 766
769 767 def has_section(self, section, untrusted=False):
770 768 '''tell whether section exists in config.'''
771 769 return section in self._data(untrusted)
772 770
773 771 def configitems(self, section, untrusted=False, ignoresub=False):
774 772 items = self._data(untrusted).items(section)
775 773 if ignoresub:
776 774 items = [i for i in items if ':' not in i[0]]
777 775 if self.debugflag and not untrusted and self._reportuntrusted:
778 776 for k, v in self._ucfg.items(section):
779 777 if self._tcfg.get(section, k) != v:
780 778 self.debug("ignoring untrusted configuration option "
781 779 "%s.%s = %s\n" % (section, k, v))
782 780 return items
783 781
784 782 def walkconfig(self, untrusted=False):
785 783 cfg = self._data(untrusted)
786 784 for section in cfg.sections():
787 785 for name, value in self.configitems(section, untrusted):
788 786 yield section, name, value
789 787
790 788 def plain(self, feature=None):
791 789 '''is plain mode active?
792 790
793 791 Plain mode means that all configuration variables which affect
794 792 the behavior and output of Mercurial should be
795 793 ignored. Additionally, the output should be stable,
796 794 reproducible and suitable for use in scripts or applications.
797 795
798 796 The only way to trigger plain mode is by setting either the
799 797 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
800 798
801 799 The return value can either be
802 800 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
803 801 - False if feature is disabled by default and not included in HGPLAIN
804 802 - True otherwise
805 803 '''
806 804 if ('HGPLAIN' not in encoding.environ and
807 805 'HGPLAINEXCEPT' not in encoding.environ):
808 806 return False
809 807 exceptions = encoding.environ.get('HGPLAINEXCEPT',
810 808 '').strip().split(',')
811 809 # TODO: add support for HGPLAIN=+feature,-feature syntax
812 810 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
813 811 exceptions.append('strictflags')
814 812 if feature and exceptions:
815 813 return feature not in exceptions
816 814 return True
817 815
818 816 def username(self, acceptempty=False):
819 817 """Return default username to be used in commits.
820 818
821 819 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
822 820 and stop searching if one of these is set.
823 821 If not found and acceptempty is True, returns None.
824 822 If not found and ui.askusername is True, ask the user, else use
825 823 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
826 824 If no username could be found, raise an Abort error.
827 825 """
828 826 user = encoding.environ.get("HGUSER")
829 827 if user is None:
830 828 user = self.config("ui", "username")
831 829 if user is not None:
832 830 user = os.path.expandvars(user)
833 831 if user is None:
834 832 user = encoding.environ.get("EMAIL")
835 833 if user is None and acceptempty:
836 834 return user
837 835 if user is None and self.configbool("ui", "askusername"):
838 836 user = self.prompt(_("enter a commit username:"), default=None)
839 837 if user is None and not self.interactive():
840 838 try:
841 839 user = '%s@%s' % (procutil.getuser(),
842 840 encoding.strtolocal(socket.getfqdn()))
843 841 self.warn(_("no username found, using '%s' instead\n") % user)
844 842 except KeyError:
845 843 pass
846 844 if not user:
847 845 raise error.Abort(_('no username supplied'),
848 846 hint=_("use 'hg config --edit' "
849 847 'to set your username'))
850 848 if "\n" in user:
851 849 raise error.Abort(_("username %r contains a newline\n")
852 850 % pycompat.bytestr(user))
853 851 return user
854 852
855 853 def shortuser(self, user):
856 854 """Return a short representation of a user name or email address."""
857 855 if not self.verbose:
858 856 user = stringutil.shortuser(user)
859 857 return user
860 858
861 859 def expandpath(self, loc, default=None):
862 860 """Return repository location relative to cwd or from [paths]"""
863 861 try:
864 862 p = self.paths.getpath(loc)
865 863 if p:
866 864 return p.rawloc
867 865 except error.RepoError:
868 866 pass
869 867
870 868 if default:
871 869 try:
872 870 p = self.paths.getpath(default)
873 871 if p:
874 872 return p.rawloc
875 873 except error.RepoError:
876 874 pass
877 875
878 876 return loc
879 877
880 878 @util.propertycache
881 879 def paths(self):
882 880 return paths(self)
883 881
884 882 def pushbuffer(self, error=False, subproc=False, labeled=False):
885 883 """install a buffer to capture standard output of the ui object
886 884
887 885 If error is True, the error output will be captured too.
888 886
889 887 If subproc is True, output from subprocesses (typically hooks) will be
890 888 captured too.
891 889
892 890 If labeled is True, any labels associated with buffered
893 891 output will be handled. By default, this has no effect
894 892 on the output returned, but extensions and GUI tools may
895 893 handle this argument and returned styled output. If output
896 894 is being buffered so it can be captured and parsed or
897 895 processed, labeled should not be set to True.
898 896 """
899 897 self._buffers.append([])
900 898 self._bufferstates.append((error, subproc, labeled))
901 899 self._bufferapplylabels = labeled
902 900
903 901 def popbuffer(self):
904 902 '''pop the last buffer and return the buffered output'''
905 903 self._bufferstates.pop()
906 904 if self._bufferstates:
907 905 self._bufferapplylabels = self._bufferstates[-1][2]
908 906 else:
909 907 self._bufferapplylabels = None
910 908
911 909 return "".join(self._buffers.pop())
912 910
913 911 def canwritewithoutlabels(self):
914 912 '''check if write skips the label'''
915 913 if self._buffers and not self._bufferapplylabels:
916 914 return True
917 915 return self._colormode is None
918 916
919 917 def canbatchlabeledwrites(self):
920 918 '''check if write calls with labels are batchable'''
921 919 # Windows color printing is special, see ``write``.
922 920 return self._colormode != 'win32'
923 921
924 922 def write(self, *args, **opts):
925 923 '''write args to output
926 924
927 925 By default, this method simply writes to the buffer or stdout.
928 926 Color mode can be set on the UI class to have the output decorated
929 927 with color modifier before being written to stdout.
930 928
931 929 The color used is controlled by an optional keyword argument, "label".
932 930 This should be a string containing label names separated by space.
933 931 Label names take the form of "topic.type". For example, ui.debug()
934 932 issues a label of "ui.debug".
935 933
936 934 When labeling output for a specific command, a label of
937 935 "cmdname.type" is recommended. For example, status issues
938 936 a label of "status.modified" for modified files.
939 937 '''
940 938 if self._buffers:
941 939 if self._bufferapplylabels:
942 940 label = opts.get(r'label', '')
943 941 self._buffers[-1].extend(self.label(a, label) for a in args)
944 942 else:
945 943 self._buffers[-1].extend(args)
946 944 else:
947 945 self._writenobuf(*args, **opts)
948 946
949 947 def _writenobuf(self, *args, **opts):
950 948 if self._colormode == 'win32':
951 949 # windows color printing is its own can of crab, defer to
952 950 # the color module and that is it.
953 951 color.win32print(self, self._write, *args, **opts)
954 952 else:
955 953 msgs = args
956 954 if self._colormode is not None:
957 955 label = opts.get(r'label', '')
958 956 msgs = [self.label(a, label) for a in args]
959 957 self._write(*msgs, **opts)
960 958
961 959 def _write(self, *msgs, **opts):
962 960 self._progclear()
963 961 # opencode timeblockedsection because this is a critical path
964 962 starttime = util.timer()
965 963 try:
966 964 self.fout.write(''.join(msgs))
967 965 except IOError as err:
968 966 raise error.StdioError(err)
969 967 finally:
970 968 self._blockedtimes['stdio_blocked'] += \
971 969 (util.timer() - starttime) * 1000
972 970
973 971 def write_err(self, *args, **opts):
974 972 self._progclear()
975 973 if self._bufferstates and self._bufferstates[-1][0]:
976 974 self.write(*args, **opts)
977 975 elif self._colormode == 'win32':
978 976 # windows color printing is its own can of crab, defer to
979 977 # the color module and that is it.
980 978 color.win32print(self, self._write_err, *args, **opts)
981 979 else:
982 980 msgs = args
983 981 if self._colormode is not None:
984 982 label = opts.get(r'label', '')
985 983 msgs = [self.label(a, label) for a in args]
986 984 self._write_err(*msgs, **opts)
987 985
988 986 def _write_err(self, *msgs, **opts):
989 987 try:
990 988 with self.timeblockedsection('stdio'):
991 989 if not getattr(self.fout, 'closed', False):
992 990 self.fout.flush()
993 991 for a in msgs:
994 992 self.ferr.write(a)
995 993 # stderr may be buffered under win32 when redirected to files,
996 994 # including stdout.
997 995 if not getattr(self.ferr, 'closed', False):
998 996 self.ferr.flush()
999 997 except IOError as inst:
1000 998 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1001 999 raise error.StdioError(inst)
1002 1000
1003 1001 def flush(self):
1004 1002 # opencode timeblockedsection because this is a critical path
1005 1003 starttime = util.timer()
1006 1004 try:
1007 1005 try:
1008 1006 self.fout.flush()
1009 1007 except IOError as err:
1010 1008 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1011 1009 raise error.StdioError(err)
1012 1010 finally:
1013 1011 try:
1014 1012 self.ferr.flush()
1015 1013 except IOError as err:
1016 1014 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1017 1015 raise error.StdioError(err)
1018 1016 finally:
1019 1017 self._blockedtimes['stdio_blocked'] += \
1020 1018 (util.timer() - starttime) * 1000
1021 1019
1022 1020 def _isatty(self, fh):
1023 1021 if self.configbool('ui', 'nontty'):
1024 1022 return False
1025 1023 return procutil.isatty(fh)
1026 1024
1027 1025 def disablepager(self):
1028 1026 self._disablepager = True
1029 1027
1030 1028 def pager(self, command):
1031 1029 """Start a pager for subsequent command output.
1032 1030
1033 1031 Commands which produce a long stream of output should call
1034 1032 this function to activate the user's preferred pagination
1035 1033 mechanism (which may be no pager). Calling this function
1036 1034 precludes any future use of interactive functionality, such as
1037 1035 prompting the user or activating curses.
1038 1036
1039 1037 Args:
1040 1038 command: The full, non-aliased name of the command. That is, "log"
1041 1039 not "history, "summary" not "summ", etc.
1042 1040 """
1043 1041 if (self._disablepager
1044 1042 or self.pageractive):
1045 1043 # how pager should do is already determined
1046 1044 return
1047 1045
1048 1046 if not command.startswith('internal-always-') and (
1049 1047 # explicit --pager=on (= 'internal-always-' prefix) should
1050 1048 # take precedence over disabling factors below
1051 1049 command in self.configlist('pager', 'ignore')
1052 1050 or not self.configbool('ui', 'paginate')
1053 1051 or not self.configbool('pager', 'attend-' + command, True)
1054 1052 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1055 1053 # formatted() will need some adjustment.
1056 1054 or not self.formatted()
1057 1055 or self.plain()
1058 1056 or self._buffers
1059 1057 # TODO: expose debugger-enabled on the UI object
1060 1058 or '--debugger' in pycompat.sysargv):
1061 1059 # We only want to paginate if the ui appears to be
1062 1060 # interactive, the user didn't say HGPLAIN or
1063 1061 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1064 1062 return
1065 1063
1066 1064 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1067 1065 if not pagercmd:
1068 1066 return
1069 1067
1070 1068 pagerenv = {}
1071 1069 for name, value in rcutil.defaultpagerenv().items():
1072 1070 if name not in encoding.environ:
1073 1071 pagerenv[name] = value
1074 1072
1075 1073 self.debug('starting pager for command %r\n' % command)
1076 1074 self.flush()
1077 1075
1078 1076 wasformatted = self.formatted()
1079 1077 if util.safehasattr(signal, "SIGPIPE"):
1080 1078 signal.signal(signal.SIGPIPE, _catchterm)
1081 1079 if self._runpager(pagercmd, pagerenv):
1082 1080 self.pageractive = True
1083 1081 # Preserve the formatted-ness of the UI. This is important
1084 1082 # because we mess with stdout, which might confuse
1085 1083 # auto-detection of things being formatted.
1086 1084 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1087 1085 self.setconfig('ui', 'interactive', False, 'pager')
1088 1086
1089 1087 # If pagermode differs from color.mode, reconfigure color now that
1090 1088 # pageractive is set.
1091 1089 cm = self._colormode
1092 1090 if cm != self.config('color', 'pagermode', cm):
1093 1091 color.setup(self)
1094 1092 else:
1095 1093 # If the pager can't be spawned in dispatch when --pager=on is
1096 1094 # given, don't try again when the command runs, to avoid a duplicate
1097 1095 # warning about a missing pager command.
1098 1096 self.disablepager()
1099 1097
1100 1098 def _runpager(self, command, env=None):
1101 1099 """Actually start the pager and set up file descriptors.
1102 1100
1103 1101 This is separate in part so that extensions (like chg) can
1104 1102 override how a pager is invoked.
1105 1103 """
1106 1104 if command == 'cat':
1107 1105 # Save ourselves some work.
1108 1106 return False
1109 1107 # If the command doesn't contain any of these characters, we
1110 1108 # assume it's a binary and exec it directly. This means for
1111 1109 # simple pager command configurations, we can degrade
1112 1110 # gracefully and tell the user about their broken pager.
1113 1111 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1114 1112
1115 1113 if pycompat.iswindows and not shell:
1116 1114 # Window's built-in `more` cannot be invoked with shell=False, but
1117 1115 # its `more.com` can. Hide this implementation detail from the
1118 1116 # user so we can also get sane bad PAGER behavior. MSYS has
1119 1117 # `more.exe`, so do a cmd.exe style resolution of the executable to
1120 1118 # determine which one to use.
1121 1119 fullcmd = procutil.findexe(command)
1122 1120 if not fullcmd:
1123 1121 self.warn(_("missing pager command '%s', skipping pager\n")
1124 1122 % command)
1125 1123 return False
1126 1124
1127 1125 command = fullcmd
1128 1126
1129 1127 try:
1130 1128 pager = subprocess.Popen(
1131 1129 command, shell=shell, bufsize=-1,
1132 1130 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1133 1131 stdout=procutil.stdout, stderr=procutil.stderr,
1134 1132 env=procutil.shellenviron(env))
1135 1133 except OSError as e:
1136 1134 if e.errno == errno.ENOENT and not shell:
1137 1135 self.warn(_("missing pager command '%s', skipping pager\n")
1138 1136 % command)
1139 1137 return False
1140 1138 raise
1141 1139
1142 1140 # back up original file descriptors
1143 1141 stdoutfd = os.dup(procutil.stdout.fileno())
1144 1142 stderrfd = os.dup(procutil.stderr.fileno())
1145 1143
1146 1144 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1147 1145 if self._isatty(procutil.stderr):
1148 1146 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1149 1147
1150 1148 @self.atexit
1151 1149 def killpager():
1152 1150 if util.safehasattr(signal, "SIGINT"):
1153 1151 signal.signal(signal.SIGINT, signal.SIG_IGN)
1154 1152 # restore original fds, closing pager.stdin copies in the process
1155 1153 os.dup2(stdoutfd, procutil.stdout.fileno())
1156 1154 os.dup2(stderrfd, procutil.stderr.fileno())
1157 1155 pager.stdin.close()
1158 1156 pager.wait()
1159 1157
1160 1158 return True
1161 1159
1162 1160 @property
1163 1161 def _exithandlers(self):
1164 1162 return _reqexithandlers
1165 1163
1166 1164 def atexit(self, func, *args, **kwargs):
1167 1165 '''register a function to run after dispatching a request
1168 1166
1169 1167 Handlers do not stay registered across request boundaries.'''
1170 1168 self._exithandlers.append((func, args, kwargs))
1171 1169 return func
1172 1170
1173 1171 def interface(self, feature):
1174 1172 """what interface to use for interactive console features?
1175 1173
1176 1174 The interface is controlled by the value of `ui.interface` but also by
1177 1175 the value of feature-specific configuration. For example:
1178 1176
1179 1177 ui.interface.histedit = text
1180 1178 ui.interface.chunkselector = curses
1181 1179
1182 1180 Here the features are "histedit" and "chunkselector".
1183 1181
1184 1182 The configuration above means that the default interfaces for commands
1185 1183 is curses, the interface for histedit is text and the interface for
1186 1184 selecting chunk is crecord (the best curses interface available).
1187 1185
1188 1186 Consider the following example:
1189 1187 ui.interface = curses
1190 1188 ui.interface.histedit = text
1191 1189
1192 1190 Then histedit will use the text interface and chunkselector will use
1193 1191 the default curses interface (crecord at the moment).
1194 1192 """
1195 1193 alldefaults = frozenset(["text", "curses"])
1196 1194
1197 1195 featureinterfaces = {
1198 1196 "chunkselector": [
1199 1197 "text",
1200 1198 "curses",
1201 1199 ]
1202 1200 }
1203 1201
1204 1202 # Feature-specific interface
1205 1203 if feature not in featureinterfaces.keys():
1206 1204 # Programming error, not user error
1207 1205 raise ValueError("Unknown feature requested %s" % feature)
1208 1206
1209 1207 availableinterfaces = frozenset(featureinterfaces[feature])
1210 1208 if alldefaults > availableinterfaces:
1211 1209 # Programming error, not user error. We need a use case to
1212 1210 # define the right thing to do here.
1213 1211 raise ValueError(
1214 1212 "Feature %s does not handle all default interfaces" %
1215 1213 feature)
1216 1214
1217 1215 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1218 1216 return "text"
1219 1217
1220 1218 # Default interface for all the features
1221 1219 defaultinterface = "text"
1222 1220 i = self.config("ui", "interface")
1223 1221 if i in alldefaults:
1224 1222 defaultinterface = i
1225 1223
1226 1224 choseninterface = defaultinterface
1227 1225 f = self.config("ui", "interface.%s" % feature)
1228 1226 if f in availableinterfaces:
1229 1227 choseninterface = f
1230 1228
1231 1229 if i is not None and defaultinterface != i:
1232 1230 if f is not None:
1233 1231 self.warn(_("invalid value for ui.interface: %s\n") %
1234 1232 (i,))
1235 1233 else:
1236 1234 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1237 1235 (i, choseninterface))
1238 1236 if f is not None and choseninterface != f:
1239 1237 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1240 1238 (feature, f, choseninterface))
1241 1239
1242 1240 return choseninterface
1243 1241
1244 1242 def interactive(self):
1245 1243 '''is interactive input allowed?
1246 1244
1247 1245 An interactive session is a session where input can be reasonably read
1248 1246 from `sys.stdin'. If this function returns false, any attempt to read
1249 1247 from stdin should fail with an error, unless a sensible default has been
1250 1248 specified.
1251 1249
1252 1250 Interactiveness is triggered by the value of the `ui.interactive'
1253 1251 configuration variable or - if it is unset - when `sys.stdin' points
1254 1252 to a terminal device.
1255 1253
1256 1254 This function refers to input only; for output, see `ui.formatted()'.
1257 1255 '''
1258 1256 i = self.configbool("ui", "interactive")
1259 1257 if i is None:
1260 1258 # some environments replace stdin without implementing isatty
1261 1259 # usually those are non-interactive
1262 1260 return self._isatty(self.fin)
1263 1261
1264 1262 return i
1265 1263
1266 1264 def termwidth(self):
1267 1265 '''how wide is the terminal in columns?
1268 1266 '''
1269 1267 if 'COLUMNS' in encoding.environ:
1270 1268 try:
1271 1269 return int(encoding.environ['COLUMNS'])
1272 1270 except ValueError:
1273 1271 pass
1274 1272 return scmutil.termsize(self)[0]
1275 1273
1276 1274 def formatted(self):
1277 1275 '''should formatted output be used?
1278 1276
1279 1277 It is often desirable to format the output to suite the output medium.
1280 1278 Examples of this are truncating long lines or colorizing messages.
1281 1279 However, this is not often not desirable when piping output into other
1282 1280 utilities, e.g. `grep'.
1283 1281
1284 1282 Formatted output is triggered by the value of the `ui.formatted'
1285 1283 configuration variable or - if it is unset - when `sys.stdout' points
1286 1284 to a terminal device. Please note that `ui.formatted' should be
1287 1285 considered an implementation detail; it is not intended for use outside
1288 1286 Mercurial or its extensions.
1289 1287
1290 1288 This function refers to output only; for input, see `ui.interactive()'.
1291 1289 This function always returns false when in plain mode, see `ui.plain()'.
1292 1290 '''
1293 1291 if self.plain():
1294 1292 return False
1295 1293
1296 1294 i = self.configbool("ui", "formatted")
1297 1295 if i is None:
1298 1296 # some environments replace stdout without implementing isatty
1299 1297 # usually those are non-interactive
1300 1298 return self._isatty(self.fout)
1301 1299
1302 1300 return i
1303 1301
1304 1302 def _readline(self):
1305 1303 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1306 1304 # because they have to be text streams with *no buffering*. Instead,
1307 1305 # we use rawinput() only if call_readline() will be invoked by
1308 1306 # PyOS_Readline(), so no I/O will be made at Python layer.
1309 1307 usereadline = (self._isatty(self.fin) and self._isatty(self.fout)
1310 1308 and procutil.isstdin(self.fin)
1311 1309 and procutil.isstdout(self.fout))
1312 1310 if usereadline:
1313 1311 try:
1314 1312 # magically add command line editing support, where
1315 1313 # available
1316 1314 import readline
1317 1315 # force demandimport to really load the module
1318 1316 readline.read_history_file
1319 1317 # windows sometimes raises something other than ImportError
1320 1318 except Exception:
1321 1319 usereadline = False
1322 1320
1323 1321 # prompt ' ' must exist; otherwise readline may delete entire line
1324 1322 # - http://bugs.python.org/issue12833
1325 1323 with self.timeblockedsection('stdio'):
1326 1324 if usereadline:
1327 1325 line = encoding.strtolocal(pycompat.rawinput(r' '))
1328 1326 # When stdin is in binary mode on Windows, it can cause
1329 1327 # raw_input() to emit an extra trailing carriage return
1330 1328 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1331 1329 line = line[:-1]
1332 1330 else:
1333 1331 self.fout.write(b' ')
1334 1332 self.fout.flush()
1335 1333 line = self.fin.readline()
1336 1334 if not line:
1337 1335 raise EOFError
1338 1336 line = line.rstrip(pycompat.oslinesep)
1339 1337
1340 1338 return line
1341 1339
1342 1340 def prompt(self, msg, default="y"):
1343 1341 """Prompt user with msg, read response.
1344 1342 If ui is not interactive, the default is returned.
1345 1343 """
1346 1344 if not self.interactive():
1347 1345 self.write(msg, ' ', default or '', "\n")
1348 1346 return default
1349 1347 self._writenobuf(msg, label='ui.prompt')
1350 1348 self.flush()
1351 1349 try:
1352 1350 r = self._readline()
1353 1351 if not r:
1354 1352 r = default
1355 1353 if self.configbool('ui', 'promptecho'):
1356 1354 self.write(r, "\n")
1357 1355 return r
1358 1356 except EOFError:
1359 1357 raise error.ResponseExpected()
1360 1358
1361 1359 @staticmethod
1362 1360 def extractchoices(prompt):
1363 1361 """Extract prompt message and list of choices from specified prompt.
1364 1362
1365 1363 This returns tuple "(message, choices)", and "choices" is the
1366 1364 list of tuple "(response character, text without &)".
1367 1365
1368 1366 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1369 1367 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1370 1368 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1371 1369 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1372 1370 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1373 1371 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1374 1372 """
1375 1373
1376 1374 # Sadly, the prompt string may have been built with a filename
1377 1375 # containing "$$" so let's try to find the first valid-looking
1378 1376 # prompt to start parsing. Sadly, we also can't rely on
1379 1377 # choices containing spaces, ASCII, or basically anything
1380 1378 # except an ampersand followed by a character.
1381 1379 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1382 1380 msg = m.group(1)
1383 1381 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1384 1382 def choicetuple(s):
1385 1383 ampidx = s.index('&')
1386 1384 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1387 1385 return (msg, [choicetuple(s) for s in choices])
1388 1386
1389 1387 def promptchoice(self, prompt, default=0):
1390 1388 """Prompt user with a message, read response, and ensure it matches
1391 1389 one of the provided choices. The prompt is formatted as follows:
1392 1390
1393 1391 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1394 1392
1395 1393 The index of the choice is returned. Responses are case
1396 1394 insensitive. If ui is not interactive, the default is
1397 1395 returned.
1398 1396 """
1399 1397
1400 1398 msg, choices = self.extractchoices(prompt)
1401 1399 resps = [r for r, t in choices]
1402 1400 while True:
1403 1401 r = self.prompt(msg, resps[default])
1404 1402 if r.lower() in resps:
1405 1403 return resps.index(r.lower())
1406 1404 self.write(_("unrecognized response\n"))
1407 1405
1408 1406 def getpass(self, prompt=None, default=None):
1409 1407 if not self.interactive():
1410 1408 return default
1411 1409 try:
1412 1410 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1413 1411 # disable getpass() only if explicitly specified. it's still valid
1414 1412 # to interact with tty even if fin is not a tty.
1415 1413 with self.timeblockedsection('stdio'):
1416 1414 if self.configbool('ui', 'nontty'):
1417 1415 l = self.fin.readline()
1418 1416 if not l:
1419 1417 raise EOFError
1420 1418 return l.rstrip('\n')
1421 1419 else:
1422 1420 return getpass.getpass('')
1423 1421 except EOFError:
1424 1422 raise error.ResponseExpected()
1425 1423 def status(self, *msg, **opts):
1426 1424 '''write status message to output (if ui.quiet is False)
1427 1425
1428 1426 This adds an output label of "ui.status".
1429 1427 '''
1430 1428 if not self.quiet:
1431 1429 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1432 1430 self.write(*msg, **opts)
1433 1431 def warn(self, *msg, **opts):
1434 1432 '''write warning message to output (stderr)
1435 1433
1436 1434 This adds an output label of "ui.warning".
1437 1435 '''
1438 1436 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1439 1437 self.write_err(*msg, **opts)
1440 1438 def note(self, *msg, **opts):
1441 1439 '''write note to output (if ui.verbose is True)
1442 1440
1443 1441 This adds an output label of "ui.note".
1444 1442 '''
1445 1443 if self.verbose:
1446 1444 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1447 1445 self.write(*msg, **opts)
1448 1446 def debug(self, *msg, **opts):
1449 1447 '''write debug message to output (if ui.debugflag is True)
1450 1448
1451 1449 This adds an output label of "ui.debug".
1452 1450 '''
1453 1451 if self.debugflag:
1454 1452 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1455 1453 self.write(*msg, **opts)
1456 1454
1457 1455 def edit(self, text, user, extra=None, editform=None, pending=None,
1458 1456 repopath=None, action=None):
1459 1457 if action is None:
1460 1458 self.develwarn('action is None but will soon be a required '
1461 1459 'parameter to ui.edit()')
1462 1460 extra_defaults = {
1463 1461 'prefix': 'editor',
1464 1462 'suffix': '.txt',
1465 1463 }
1466 1464 if extra is not None:
1467 1465 if extra.get('suffix') is not None:
1468 1466 self.develwarn('extra.suffix is not None but will soon be '
1469 1467 'ignored by ui.edit()')
1470 1468 extra_defaults.update(extra)
1471 1469 extra = extra_defaults
1472 1470
1473 1471 if action == 'diff':
1474 1472 suffix = '.diff'
1475 1473 elif action:
1476 1474 suffix = '.%s.hg.txt' % action
1477 1475 else:
1478 1476 suffix = extra['suffix']
1479 1477
1480 1478 rdir = None
1481 1479 if self.configbool('experimental', 'editortmpinhg'):
1482 1480 rdir = repopath
1483 1481 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1484 1482 suffix=suffix,
1485 1483 dir=rdir)
1486 1484 try:
1487 1485 f = os.fdopen(fd, r'wb')
1488 1486 f.write(util.tonativeeol(text))
1489 1487 f.close()
1490 1488
1491 1489 environ = {'HGUSER': user}
1492 1490 if 'transplant_source' in extra:
1493 1491 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1494 1492 for label in ('intermediate-source', 'source', 'rebase_source'):
1495 1493 if label in extra:
1496 1494 environ.update({'HGREVISION': extra[label]})
1497 1495 break
1498 1496 if editform:
1499 1497 environ.update({'HGEDITFORM': editform})
1500 1498 if pending:
1501 1499 environ.update({'HG_PENDING': pending})
1502 1500
1503 1501 editor = self.geteditor()
1504 1502
1505 1503 self.system("%s \"%s\"" % (editor, name),
1506 1504 environ=environ,
1507 1505 onerr=error.Abort, errprefix=_("edit failed"),
1508 1506 blockedtag='editor')
1509 1507
1510 1508 f = open(name, r'rb')
1511 1509 t = util.fromnativeeol(f.read())
1512 1510 f.close()
1513 1511 finally:
1514 1512 os.unlink(name)
1515 1513
1516 1514 return t
1517 1515
1518 1516 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1519 1517 blockedtag=None):
1520 1518 '''execute shell command with appropriate output stream. command
1521 1519 output will be redirected if fout is not stdout.
1522 1520
1523 1521 if command fails and onerr is None, return status, else raise onerr
1524 1522 object as exception.
1525 1523 '''
1526 1524 if blockedtag is None:
1527 1525 # Long cmds tend to be because of an absolute path on cmd. Keep
1528 1526 # the tail end instead
1529 1527 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1530 1528 blockedtag = 'unknown_system_' + cmdsuffix
1531 1529 out = self.fout
1532 1530 if any(s[1] for s in self._bufferstates):
1533 1531 out = self
1534 1532 with self.timeblockedsection(blockedtag):
1535 1533 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1536 1534 if rc and onerr:
1537 1535 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1538 1536 procutil.explainexit(rc))
1539 1537 if errprefix:
1540 1538 errmsg = '%s: %s' % (errprefix, errmsg)
1541 1539 raise onerr(errmsg)
1542 1540 return rc
1543 1541
1544 1542 def _runsystem(self, cmd, environ, cwd, out):
1545 1543 """actually execute the given shell command (can be overridden by
1546 1544 extensions like chg)"""
1547 1545 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1548 1546
1549 1547 def traceback(self, exc=None, force=False):
1550 1548 '''print exception traceback if traceback printing enabled or forced.
1551 1549 only to call in exception handler. returns true if traceback
1552 1550 printed.'''
1553 1551 if self.tracebackflag or force:
1554 1552 if exc is None:
1555 1553 exc = sys.exc_info()
1556 1554 cause = getattr(exc[1], 'cause', None)
1557 1555
1558 1556 if cause is not None:
1559 1557 causetb = traceback.format_tb(cause[2])
1560 1558 exctb = traceback.format_tb(exc[2])
1561 1559 exconly = traceback.format_exception_only(cause[0], cause[1])
1562 1560
1563 1561 # exclude frame where 'exc' was chained and rethrown from exctb
1564 1562 self.write_err('Traceback (most recent call last):\n',
1565 1563 ''.join(exctb[:-1]),
1566 1564 ''.join(causetb),
1567 1565 ''.join(exconly))
1568 1566 else:
1569 1567 output = traceback.format_exception(exc[0], exc[1], exc[2])
1570 1568 self.write_err(encoding.strtolocal(r''.join(output)))
1571 1569 return self.tracebackflag or force
1572 1570
1573 1571 def geteditor(self):
1574 1572 '''return editor to use'''
1575 1573 if pycompat.sysplatform == 'plan9':
1576 1574 # vi is the MIPS instruction simulator on Plan 9. We
1577 1575 # instead default to E to plumb commit messages to
1578 1576 # avoid confusion.
1579 1577 editor = 'E'
1580 1578 else:
1581 1579 editor = 'vi'
1582 1580 return (encoding.environ.get("HGEDITOR") or
1583 1581 self.config("ui", "editor", editor))
1584 1582
1585 1583 @util.propertycache
1586 1584 def _progbar(self):
1587 1585 """setup the progbar singleton to the ui object"""
1588 1586 if (self.quiet or self.debugflag
1589 1587 or self.configbool('progress', 'disable')
1590 1588 or not progress.shouldprint(self)):
1591 1589 return None
1592 1590 return getprogbar(self)
1593 1591
1594 1592 def _progclear(self):
1595 1593 """clear progress bar output if any. use it before any output"""
1596 1594 if not haveprogbar(): # nothing loaded yet
1597 1595 return
1598 1596 if self._progbar is not None and self._progbar.printed:
1599 1597 self._progbar.clear()
1600 1598
1601 1599 def progress(self, topic, pos, item="", unit="", total=None):
1602 1600 '''show a progress message
1603 1601
1604 1602 By default a textual progress bar will be displayed if an operation
1605 1603 takes too long. 'topic' is the current operation, 'item' is a
1606 1604 non-numeric marker of the current position (i.e. the currently
1607 1605 in-process file), 'pos' is the current numeric position (i.e.
1608 1606 revision, bytes, etc.), unit is a corresponding unit label,
1609 1607 and total is the highest expected pos.
1610 1608
1611 1609 Multiple nested topics may be active at a time.
1612 1610
1613 1611 All topics should be marked closed by setting pos to None at
1614 1612 termination.
1615 1613 '''
1616 1614 if self._progbar is not None:
1617 1615 self._progbar.progress(topic, pos, item=item, unit=unit,
1618 1616 total=total)
1619 1617 if pos is None or not self.configbool('progress', 'debug'):
1620 1618 return
1621 1619
1622 1620 if unit:
1623 1621 unit = ' ' + unit
1624 1622 if item:
1625 1623 item = ' ' + item
1626 1624
1627 1625 if total:
1628 1626 pct = 100.0 * pos / total
1629 1627 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1630 1628 % (topic, item, pos, total, unit, pct))
1631 1629 else:
1632 1630 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1633 1631
1634 1632 def makeprogress(self, topic, unit="", total=None):
1635 1633 '''exists only so low-level modules won't need to import scmutil'''
1636 1634 return scmutil.progress(self, topic, unit, total)
1637 1635
1638 1636 def log(self, service, *msg, **opts):
1639 1637 '''hook for logging facility extensions
1640 1638
1641 1639 service should be a readily-identifiable subsystem, which will
1642 1640 allow filtering.
1643 1641
1644 1642 *msg should be a newline-terminated format string to log, and
1645 1643 then any values to %-format into that format string.
1646 1644
1647 1645 **opts currently has no defined meanings.
1648 1646 '''
1649 1647
1650 1648 def label(self, msg, label):
1651 1649 '''style msg based on supplied label
1652 1650
1653 1651 If some color mode is enabled, this will add the necessary control
1654 1652 characters to apply such color. In addition, 'debug' color mode adds
1655 1653 markup showing which label affects a piece of text.
1656 1654
1657 1655 ui.write(s, 'label') is equivalent to
1658 1656 ui.write(ui.label(s, 'label')).
1659 1657 '''
1660 1658 if self._colormode is not None:
1661 1659 return color.colorlabel(self, msg, label)
1662 1660 return msg
1663 1661
1664 1662 def develwarn(self, msg, stacklevel=1, config=None):
1665 1663 """issue a developer warning message
1666 1664
1667 1665 Use 'stacklevel' to report the offender some layers further up in the
1668 1666 stack.
1669 1667 """
1670 1668 if not self.configbool('devel', 'all-warnings'):
1671 1669 if config is None or not self.configbool('devel', config):
1672 1670 return
1673 1671 msg = 'devel-warn: ' + msg
1674 1672 stacklevel += 1 # get in develwarn
1675 1673 if self.tracebackflag:
1676 1674 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1677 1675 self.log('develwarn', '%s at:\n%s' %
1678 1676 (msg, ''.join(util.getstackframes(stacklevel))))
1679 1677 else:
1680 1678 curframe = inspect.currentframe()
1681 1679 calframe = inspect.getouterframes(curframe, 2)
1682 1680 fname, lineno, fmsg = calframe[stacklevel][1:4]
1683 1681 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1684 1682 self.write_err('%s at: %s:%d (%s)\n'
1685 1683 % (msg, fname, lineno, fmsg))
1686 1684 self.log('develwarn', '%s at: %s:%d (%s)\n',
1687 1685 msg, fname, lineno, fmsg)
1688 1686 curframe = calframe = None # avoid cycles
1689 1687
1690 1688 def deprecwarn(self, msg, version, stacklevel=2):
1691 1689 """issue a deprecation warning
1692 1690
1693 1691 - msg: message explaining what is deprecated and how to upgrade,
1694 1692 - version: last version where the API will be supported,
1695 1693 """
1696 1694 if not (self.configbool('devel', 'all-warnings')
1697 1695 or self.configbool('devel', 'deprec-warn')):
1698 1696 return
1699 1697 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1700 1698 " update your code.)") % version
1701 1699 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1702 1700
1703 1701 def exportableenviron(self):
1704 1702 """The environment variables that are safe to export, e.g. through
1705 1703 hgweb.
1706 1704 """
1707 1705 return self._exportableenviron
1708 1706
1709 1707 @contextlib.contextmanager
1710 1708 def configoverride(self, overrides, source=""):
1711 1709 """Context manager for temporary config overrides
1712 1710 `overrides` must be a dict of the following structure:
1713 1711 {(section, name) : value}"""
1714 1712 backups = {}
1715 1713 try:
1716 1714 for (section, name), value in overrides.items():
1717 1715 backups[(section, name)] = self.backupconfig(section, name)
1718 1716 self.setconfig(section, name, value, source)
1719 1717 yield
1720 1718 finally:
1721 1719 for __, backup in backups.items():
1722 1720 self.restoreconfig(backup)
1723 1721 # just restoring ui.quiet config to the previous value is not enough
1724 1722 # as it does not update ui.quiet class member
1725 1723 if ('ui', 'quiet') in overrides:
1726 1724 self.fixconfig(section='ui')
1727 1725
1728 1726 class paths(dict):
1729 1727 """Represents a collection of paths and their configs.
1730 1728
1731 1729 Data is initially derived from ui instances and the config files they have
1732 1730 loaded.
1733 1731 """
1734 1732 def __init__(self, ui):
1735 1733 dict.__init__(self)
1736 1734
1737 1735 for name, loc in ui.configitems('paths', ignoresub=True):
1738 1736 # No location is the same as not existing.
1739 1737 if not loc:
1740 1738 continue
1741 1739 loc, sub = ui.configsuboptions('paths', name)
1742 1740 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1743 1741
1744 1742 def getpath(self, name, default=None):
1745 1743 """Return a ``path`` from a string, falling back to default.
1746 1744
1747 1745 ``name`` can be a named path or locations. Locations are filesystem
1748 1746 paths or URIs.
1749 1747
1750 1748 Returns None if ``name`` is not a registered path, a URI, or a local
1751 1749 path to a repo.
1752 1750 """
1753 1751 # Only fall back to default if no path was requested.
1754 1752 if name is None:
1755 1753 if not default:
1756 1754 default = ()
1757 1755 elif not isinstance(default, (tuple, list)):
1758 1756 default = (default,)
1759 1757 for k in default:
1760 1758 try:
1761 1759 return self[k]
1762 1760 except KeyError:
1763 1761 continue
1764 1762 return None
1765 1763
1766 1764 # Most likely empty string.
1767 1765 # This may need to raise in the future.
1768 1766 if not name:
1769 1767 return None
1770 1768
1771 1769 try:
1772 1770 return self[name]
1773 1771 except KeyError:
1774 1772 # Try to resolve as a local path or URI.
1775 1773 try:
1776 1774 # We don't pass sub-options in, so no need to pass ui instance.
1777 1775 return path(None, None, rawloc=name)
1778 1776 except ValueError:
1779 1777 raise error.RepoError(_('repository %s does not exist') %
1780 1778 name)
1781 1779
1782 1780 _pathsuboptions = {}
1783 1781
1784 1782 def pathsuboption(option, attr):
1785 1783 """Decorator used to declare a path sub-option.
1786 1784
1787 1785 Arguments are the sub-option name and the attribute it should set on
1788 1786 ``path`` instances.
1789 1787
1790 1788 The decorated function will receive as arguments a ``ui`` instance,
1791 1789 ``path`` instance, and the string value of this option from the config.
1792 1790 The function should return the value that will be set on the ``path``
1793 1791 instance.
1794 1792
1795 1793 This decorator can be used to perform additional verification of
1796 1794 sub-options and to change the type of sub-options.
1797 1795 """
1798 1796 def register(func):
1799 1797 _pathsuboptions[option] = (attr, func)
1800 1798 return func
1801 1799 return register
1802 1800
1803 1801 @pathsuboption('pushurl', 'pushloc')
1804 1802 def pushurlpathoption(ui, path, value):
1805 1803 u = util.url(value)
1806 1804 # Actually require a URL.
1807 1805 if not u.scheme:
1808 1806 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1809 1807 return None
1810 1808
1811 1809 # Don't support the #foo syntax in the push URL to declare branch to
1812 1810 # push.
1813 1811 if u.fragment:
1814 1812 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1815 1813 'ignoring)\n') % path.name)
1816 1814 u.fragment = None
1817 1815
1818 1816 return bytes(u)
1819 1817
1820 1818 @pathsuboption('pushrev', 'pushrev')
1821 1819 def pushrevpathoption(ui, path, value):
1822 1820 return value
1823 1821
1824 1822 class path(object):
1825 1823 """Represents an individual path and its configuration."""
1826 1824
1827 1825 def __init__(self, ui, name, rawloc=None, suboptions=None):
1828 1826 """Construct a path from its config options.
1829 1827
1830 1828 ``ui`` is the ``ui`` instance the path is coming from.
1831 1829 ``name`` is the symbolic name of the path.
1832 1830 ``rawloc`` is the raw location, as defined in the config.
1833 1831 ``pushloc`` is the raw locations pushes should be made to.
1834 1832
1835 1833 If ``name`` is not defined, we require that the location be a) a local
1836 1834 filesystem path with a .hg directory or b) a URL. If not,
1837 1835 ``ValueError`` is raised.
1838 1836 """
1839 1837 if not rawloc:
1840 1838 raise ValueError('rawloc must be defined')
1841 1839
1842 1840 # Locations may define branches via syntax <base>#<branch>.
1843 1841 u = util.url(rawloc)
1844 1842 branch = None
1845 1843 if u.fragment:
1846 1844 branch = u.fragment
1847 1845 u.fragment = None
1848 1846
1849 1847 self.url = u
1850 1848 self.branch = branch
1851 1849
1852 1850 self.name = name
1853 1851 self.rawloc = rawloc
1854 1852 self.loc = '%s' % u
1855 1853
1856 1854 # When given a raw location but not a symbolic name, validate the
1857 1855 # location is valid.
1858 1856 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1859 1857 raise ValueError('location is not a URL or path to a local '
1860 1858 'repo: %s' % rawloc)
1861 1859
1862 1860 suboptions = suboptions or {}
1863 1861
1864 1862 # Now process the sub-options. If a sub-option is registered, its
1865 1863 # attribute will always be present. The value will be None if there
1866 1864 # was no valid sub-option.
1867 1865 for suboption, (attr, func) in _pathsuboptions.iteritems():
1868 1866 if suboption not in suboptions:
1869 1867 setattr(self, attr, None)
1870 1868 continue
1871 1869
1872 1870 value = func(ui, self, suboptions[suboption])
1873 1871 setattr(self, attr, value)
1874 1872
1875 1873 def _isvalidlocalpath(self, path):
1876 1874 """Returns True if the given path is a potentially valid repository.
1877 1875 This is its own function so that extensions can change the definition of
1878 1876 'valid' in this case (like when pulling from a git repo into a hg
1879 1877 one)."""
1880 1878 return os.path.isdir(os.path.join(path, '.hg'))
1881 1879
1882 1880 @property
1883 1881 def suboptions(self):
1884 1882 """Return sub-options and their values for this path.
1885 1883
1886 1884 This is intended to be used for presentation purposes.
1887 1885 """
1888 1886 d = {}
1889 1887 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1890 1888 value = getattr(self, attr)
1891 1889 if value is not None:
1892 1890 d[subopt] = value
1893 1891 return d
1894 1892
1895 1893 # we instantiate one globally shared progress bar to avoid
1896 1894 # competing progress bars when multiple UI objects get created
1897 1895 _progresssingleton = None
1898 1896
1899 1897 def getprogbar(ui):
1900 1898 global _progresssingleton
1901 1899 if _progresssingleton is None:
1902 1900 # passing 'ui' object to the singleton is fishy,
1903 1901 # this is how the extension used to work but feel free to rework it.
1904 1902 _progresssingleton = progress.progbar(ui)
1905 1903 return _progresssingleton
1906 1904
1907 1905 def haveprogbar():
1908 1906 return _progresssingleton is not None
@@ -1,614 +1,623 b''
1 1 $ hg init repo1
2 2 $ cd repo1
3 3 $ mkdir a b a/1 b/1 b/2
4 4 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
5 5
6 6 hg status in repo root:
7 7
8 8 $ hg status
9 9 ? a/1/in_a_1
10 10 ? a/in_a
11 11 ? b/1/in_b_1
12 12 ? b/2/in_b_2
13 13 ? b/in_b
14 14 ? in_root
15 15
16 16 hg status . in repo root:
17 17
18 18 $ hg status .
19 19 ? a/1/in_a_1
20 20 ? a/in_a
21 21 ? b/1/in_b_1
22 22 ? b/2/in_b_2
23 23 ? b/in_b
24 24 ? in_root
25 25
26 26 $ hg status --cwd a
27 27 ? a/1/in_a_1
28 28 ? a/in_a
29 29 ? b/1/in_b_1
30 30 ? b/2/in_b_2
31 31 ? b/in_b
32 32 ? in_root
33 33 $ hg status --cwd a .
34 34 ? 1/in_a_1
35 35 ? in_a
36 36 $ hg status --cwd a ..
37 37 ? 1/in_a_1
38 38 ? in_a
39 39 ? ../b/1/in_b_1
40 40 ? ../b/2/in_b_2
41 41 ? ../b/in_b
42 42 ? ../in_root
43 43
44 44 $ hg status --cwd b
45 45 ? a/1/in_a_1
46 46 ? a/in_a
47 47 ? b/1/in_b_1
48 48 ? b/2/in_b_2
49 49 ? b/in_b
50 50 ? in_root
51 51 $ hg status --cwd b .
52 52 ? 1/in_b_1
53 53 ? 2/in_b_2
54 54 ? in_b
55 55 $ hg status --cwd b ..
56 56 ? ../a/1/in_a_1
57 57 ? ../a/in_a
58 58 ? 1/in_b_1
59 59 ? 2/in_b_2
60 60 ? in_b
61 61 ? ../in_root
62 62
63 63 $ hg status --cwd a/1
64 64 ? a/1/in_a_1
65 65 ? a/in_a
66 66 ? b/1/in_b_1
67 67 ? b/2/in_b_2
68 68 ? b/in_b
69 69 ? in_root
70 70 $ hg status --cwd a/1 .
71 71 ? in_a_1
72 72 $ hg status --cwd a/1 ..
73 73 ? in_a_1
74 74 ? ../in_a
75 75
76 76 $ hg status --cwd b/1
77 77 ? a/1/in_a_1
78 78 ? a/in_a
79 79 ? b/1/in_b_1
80 80 ? b/2/in_b_2
81 81 ? b/in_b
82 82 ? in_root
83 83 $ hg status --cwd b/1 .
84 84 ? in_b_1
85 85 $ hg status --cwd b/1 ..
86 86 ? in_b_1
87 87 ? ../2/in_b_2
88 88 ? ../in_b
89 89
90 90 $ hg status --cwd b/2
91 91 ? a/1/in_a_1
92 92 ? a/in_a
93 93 ? b/1/in_b_1
94 94 ? b/2/in_b_2
95 95 ? b/in_b
96 96 ? in_root
97 97 $ hg status --cwd b/2 .
98 98 ? in_b_2
99 99 $ hg status --cwd b/2 ..
100 100 ? ../1/in_b_1
101 101 ? in_b_2
102 102 ? ../in_b
103 103
104 104 combining patterns with root and patterns without a root works
105 105
106 106 $ hg st a/in_a re:.*b$
107 107 ? a/in_a
108 108 ? b/in_b
109 109
110 110 tweaking defaults works
111 111 $ hg status --cwd a --config ui.tweakdefaults=yes
112 ? .
113 ? ../b/
112 ? 1/in_a_1
113 ? in_a
114 ? ../b/1/in_b_1
115 ? ../b/2/in_b_2
116 ? ../b/in_b
114 117 ? ../in_root
115 118 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
116 119 ? a/1/in_a_1 (glob)
117 120 ? a/in_a (glob)
118 121 ? b/1/in_b_1 (glob)
119 122 ? b/2/in_b_2 (glob)
120 123 ? b/in_b (glob)
121 124 ? in_root
122 125 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
123 ? .
124 ? ../b/
126 ? 1/in_a_1
127 ? in_a
128 ? ../b/1/in_b_1
129 ? ../b/2/in_b_2
130 ? ../b/in_b
125 131 ? ../in_root (glob)
126 132
127 133 relative paths can be requested
128 134
129 135 $ cat >> $HGRCPATH <<EOF
130 136 > [commands]
131 137 > status.relative = True
132 138 > EOF
133 139 $ hg status --cwd a
134 140 ? 1/in_a_1
135 141 ? in_a
136 142 ? ../b/1/in_b_1
137 143 ? ../b/2/in_b_2
138 144 ? ../b/in_b
139 145 ? ../in_root
140 146 $ HGPLAIN=1 hg status --cwd a
141 147 ? a/1/in_a_1 (glob)
142 148 ? a/in_a (glob)
143 149 ? b/1/in_b_1 (glob)
144 150 ? b/2/in_b_2 (glob)
145 151 ? b/in_b (glob)
146 152 ? in_root
147 153
148 154 if relative paths are explicitly off, tweakdefaults doesn't change it
149 155 $ cat >> $HGRCPATH <<EOF
150 156 > [commands]
151 157 > status.relative = False
152 158 > EOF
153 159 $ hg status --cwd a --config ui.tweakdefaults=yes
154 ? a/
155 ? b/
160 ? a/1/in_a_1
161 ? a/in_a
162 ? b/1/in_b_1
163 ? b/2/in_b_2
164 ? b/in_b
156 165 ? in_root
157 166
158 167 $ cd ..
159 168
160 169 $ hg init repo2
161 170 $ cd repo2
162 171 $ touch modified removed deleted ignored
163 172 $ echo "^ignored$" > .hgignore
164 173 $ hg ci -A -m 'initial checkin'
165 174 adding .hgignore
166 175 adding deleted
167 176 adding modified
168 177 adding removed
169 178 $ touch modified added unknown ignored
170 179 $ hg add added
171 180 $ hg remove removed
172 181 $ rm deleted
173 182
174 183 hg status:
175 184
176 185 $ hg status
177 186 A added
178 187 R removed
179 188 ! deleted
180 189 ? unknown
181 190
182 191 hg status modified added removed deleted unknown never-existed ignored:
183 192
184 193 $ hg status modified added removed deleted unknown never-existed ignored
185 194 never-existed: * (glob)
186 195 A added
187 196 R removed
188 197 ! deleted
189 198 ? unknown
190 199
191 200 $ hg copy modified copied
192 201
193 202 hg status -C:
194 203
195 204 $ hg status -C
196 205 A added
197 206 A copied
198 207 modified
199 208 R removed
200 209 ! deleted
201 210 ? unknown
202 211
203 212 hg status -A:
204 213
205 214 $ hg status -A
206 215 A added
207 216 A copied
208 217 modified
209 218 R removed
210 219 ! deleted
211 220 ? unknown
212 221 I ignored
213 222 C .hgignore
214 223 C modified
215 224
216 225 $ hg status -A -T '{status} {path} {node|shortest}\n'
217 226 A added ffff
218 227 A copied ffff
219 228 R removed ffff
220 229 ! deleted ffff
221 230 ? unknown ffff
222 231 I ignored ffff
223 232 C .hgignore ffff
224 233 C modified ffff
225 234
226 235 $ hg status -A -Tjson
227 236 [
228 237 {
229 238 "path": "added",
230 239 "status": "A"
231 240 },
232 241 {
233 242 "copy": "modified",
234 243 "path": "copied",
235 244 "status": "A"
236 245 },
237 246 {
238 247 "path": "removed",
239 248 "status": "R"
240 249 },
241 250 {
242 251 "path": "deleted",
243 252 "status": "!"
244 253 },
245 254 {
246 255 "path": "unknown",
247 256 "status": "?"
248 257 },
249 258 {
250 259 "path": "ignored",
251 260 "status": "I"
252 261 },
253 262 {
254 263 "path": ".hgignore",
255 264 "status": "C"
256 265 },
257 266 {
258 267 "path": "modified",
259 268 "status": "C"
260 269 }
261 270 ]
262 271
263 272 $ hg status -A -Tpickle > pickle
264 273 >>> from __future__ import print_function
265 274 >>> import pickle
266 275 >>> print(sorted((x['status'], x['path']) for x in pickle.load(open("pickle"))))
267 276 [('!', 'deleted'), ('?', 'pickle'), ('?', 'unknown'), ('A', 'added'), ('A', 'copied'), ('C', '.hgignore'), ('C', 'modified'), ('I', 'ignored'), ('R', 'removed')]
268 277 $ rm pickle
269 278
270 279 $ echo "^ignoreddir$" > .hgignore
271 280 $ mkdir ignoreddir
272 281 $ touch ignoreddir/file
273 282
274 283 Test templater support:
275 284
276 285 $ hg status -AT "[{status}]\t{if(copy, '{copy} -> ')}{path}\n"
277 286 [M] .hgignore
278 287 [A] added
279 288 [A] modified -> copied
280 289 [R] removed
281 290 [!] deleted
282 291 [?] ignored
283 292 [?] unknown
284 293 [I] ignoreddir/file
285 294 [C] modified
286 295 $ hg status -AT default
287 296 M .hgignore
288 297 A added
289 298 A copied
290 299 modified
291 300 R removed
292 301 ! deleted
293 302 ? ignored
294 303 ? unknown
295 304 I ignoreddir/file
296 305 C modified
297 306 $ hg status -T compact
298 307 abort: "status" not in template map
299 308 [255]
300 309
301 310 hg status ignoreddir/file:
302 311
303 312 $ hg status ignoreddir/file
304 313
305 314 hg status -i ignoreddir/file:
306 315
307 316 $ hg status -i ignoreddir/file
308 317 I ignoreddir/file
309 318 $ cd ..
310 319
311 320 Check 'status -q' and some combinations
312 321
313 322 $ hg init repo3
314 323 $ cd repo3
315 324 $ touch modified removed deleted ignored
316 325 $ echo "^ignored$" > .hgignore
317 326 $ hg commit -A -m 'initial checkin'
318 327 adding .hgignore
319 328 adding deleted
320 329 adding modified
321 330 adding removed
322 331 $ touch added unknown ignored
323 332 $ hg add added
324 333 $ echo "test" >> modified
325 334 $ hg remove removed
326 335 $ rm deleted
327 336 $ hg copy modified copied
328 337
329 338 Specify working directory revision explicitly, that should be the same as
330 339 "hg status"
331 340
332 341 $ hg status --change "wdir()"
333 342 M modified
334 343 A added
335 344 A copied
336 345 R removed
337 346 ! deleted
338 347 ? unknown
339 348
340 349 Run status with 2 different flags.
341 350 Check if result is the same or different.
342 351 If result is not as expected, raise error
343 352
344 353 $ assert() {
345 354 > hg status $1 > ../a
346 355 > hg status $2 > ../b
347 356 > if diff ../a ../b > /dev/null; then
348 357 > out=0
349 358 > else
350 359 > out=1
351 360 > fi
352 361 > if [ $3 -eq 0 ]; then
353 362 > df="same"
354 363 > else
355 364 > df="different"
356 365 > fi
357 366 > if [ $out -ne $3 ]; then
358 367 > echo "Error on $1 and $2, should be $df."
359 368 > fi
360 369 > }
361 370
362 371 Assert flag1 flag2 [0-same | 1-different]
363 372
364 373 $ assert "-q" "-mard" 0
365 374 $ assert "-A" "-marduicC" 0
366 375 $ assert "-qA" "-mardcC" 0
367 376 $ assert "-qAui" "-A" 0
368 377 $ assert "-qAu" "-marducC" 0
369 378 $ assert "-qAi" "-mardicC" 0
370 379 $ assert "-qu" "-u" 0
371 380 $ assert "-q" "-u" 1
372 381 $ assert "-m" "-a" 1
373 382 $ assert "-r" "-d" 1
374 383 $ cd ..
375 384
376 385 $ hg init repo4
377 386 $ cd repo4
378 387 $ touch modified removed deleted
379 388 $ hg ci -q -A -m 'initial checkin'
380 389 $ touch added unknown
381 390 $ hg add added
382 391 $ hg remove removed
383 392 $ rm deleted
384 393 $ echo x > modified
385 394 $ hg copy modified copied
386 395 $ hg ci -m 'test checkin' -d "1000001 0"
387 396 $ rm *
388 397 $ touch unrelated
389 398 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
390 399
391 400 hg status --change 1:
392 401
393 402 $ hg status --change 1
394 403 M modified
395 404 A added
396 405 A copied
397 406 R removed
398 407
399 408 hg status --change 1 unrelated:
400 409
401 410 $ hg status --change 1 unrelated
402 411
403 412 hg status -C --change 1 added modified copied removed deleted:
404 413
405 414 $ hg status -C --change 1 added modified copied removed deleted
406 415 M modified
407 416 A added
408 417 A copied
409 418 modified
410 419 R removed
411 420
412 421 hg status -A --change 1 and revset:
413 422
414 423 $ hg status -A --change '1|1'
415 424 M modified
416 425 A added
417 426 A copied
418 427 modified
419 428 R removed
420 429 C deleted
421 430
422 431 $ cd ..
423 432
424 433 hg status with --rev and reverted changes:
425 434
426 435 $ hg init reverted-changes-repo
427 436 $ cd reverted-changes-repo
428 437 $ echo a > file
429 438 $ hg add file
430 439 $ hg ci -m a
431 440 $ echo b > file
432 441 $ hg ci -m b
433 442
434 443 reverted file should appear clean
435 444
436 445 $ hg revert -r 0 .
437 446 reverting file
438 447 $ hg status -A --rev 0
439 448 C file
440 449
441 450 #if execbit
442 451 reverted file with changed flag should appear modified
443 452
444 453 $ chmod +x file
445 454 $ hg status -A --rev 0
446 455 M file
447 456
448 457 $ hg revert -r 0 .
449 458 reverting file
450 459
451 460 reverted and committed file with changed flag should appear modified
452 461
453 462 $ hg co -C .
454 463 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
455 464 $ chmod +x file
456 465 $ hg ci -m 'change flag'
457 466 $ hg status -A --rev 1 --rev 2
458 467 M file
459 468 $ hg diff -r 1 -r 2
460 469
461 470 #endif
462 471
463 472 $ cd ..
464 473
465 474 hg status of binary file starting with '\1\n', a separator for metadata:
466 475
467 476 $ hg init repo5
468 477 $ cd repo5
469 478 >>> open("010a", r"wb").write(b"\1\nfoo") and None
470 479 $ hg ci -q -A -m 'initial checkin'
471 480 $ hg status -A
472 481 C 010a
473 482
474 483 >>> open("010a", r"wb").write(b"\1\nbar") and None
475 484 $ hg status -A
476 485 M 010a
477 486 $ hg ci -q -m 'modify 010a'
478 487 $ hg status -A --rev 0:1
479 488 M 010a
480 489
481 490 $ touch empty
482 491 $ hg ci -q -A -m 'add another file'
483 492 $ hg status -A --rev 1:2 010a
484 493 C 010a
485 494
486 495 $ cd ..
487 496
488 497 test "hg status" with "directory pattern" which matches against files
489 498 only known on target revision.
490 499
491 500 $ hg init repo6
492 501 $ cd repo6
493 502
494 503 $ echo a > a.txt
495 504 $ hg add a.txt
496 505 $ hg commit -m '#0'
497 506 $ mkdir -p 1/2/3/4/5
498 507 $ echo b > 1/2/3/4/5/b.txt
499 508 $ hg add 1/2/3/4/5/b.txt
500 509 $ hg commit -m '#1'
501 510
502 511 $ hg update -C 0 > /dev/null
503 512 $ hg status -A
504 513 C a.txt
505 514
506 515 the directory matching against specified pattern should be removed,
507 516 because directory existence prevents 'dirstate.walk()' from showing
508 517 warning message about such pattern.
509 518
510 519 $ test ! -d 1
511 520 $ hg status -A --rev 1 1/2/3/4/5/b.txt
512 521 R 1/2/3/4/5/b.txt
513 522 $ hg status -A --rev 1 1/2/3/4/5
514 523 R 1/2/3/4/5/b.txt
515 524 $ hg status -A --rev 1 1/2/3
516 525 R 1/2/3/4/5/b.txt
517 526 $ hg status -A --rev 1 1
518 527 R 1/2/3/4/5/b.txt
519 528
520 529 $ hg status --config ui.formatdebug=True --rev 1 1
521 530 status = [
522 531 {*'path': '1/2/3/4/5/b.txt'*}, (glob)
523 532 ]
524 533
525 534 #if windows
526 535 $ hg --config ui.slash=false status -A --rev 1 1
527 536 R 1\2\3\4\5\b.txt
528 537 #endif
529 538
530 539 $ cd ..
531 540
532 541 Status after move overwriting a file (issue4458)
533 542 =================================================
534 543
535 544
536 545 $ hg init issue4458
537 546 $ cd issue4458
538 547 $ echo a > a
539 548 $ echo b > b
540 549 $ hg commit -Am base
541 550 adding a
542 551 adding b
543 552
544 553
545 554 with --force
546 555
547 556 $ hg mv b --force a
548 557 $ hg st --copies
549 558 M a
550 559 b
551 560 R b
552 561 $ hg revert --all
553 562 reverting a
554 563 undeleting b
555 564 $ rm *.orig
556 565
557 566 without force
558 567
559 568 $ hg rm a
560 569 $ hg st --copies
561 570 R a
562 571 $ hg mv b a
563 572 $ hg st --copies
564 573 M a
565 574 b
566 575 R b
567 576
568 577 using ui.statuscopies setting
569 578 $ hg st --config ui.statuscopies=true
570 579 M a
571 580 b
572 581 R b
573 582 $ hg st --config ui.statuscopies=false
574 583 M a
575 584 R b
576 585 $ hg st --config ui.tweakdefaults=yes
577 586 M a
578 587 b
579 588 R b
580 589
581 590 using log status template (issue5155)
582 591 $ hg log -Tstatus -r 'wdir()' -C
583 592 changeset: 2147483647:ffffffffffff
584 593 parent: 0:8c55c58b4c0e
585 594 user: test
586 595 date: * (glob)
587 596 files:
588 597 M a
589 598 b
590 599 R b
591 600
592 601
593 602 Other "bug" highlight, the revision status does not report the copy information.
594 603 This is buggy behavior.
595 604
596 605 $ hg commit -m 'blah'
597 606 $ hg st --copies --change .
598 607 M a
599 608 R b
600 609
601 610 using log status template, the copy information is displayed correctly.
602 611 $ hg log -Tstatus -r. -C
603 612 changeset: 1:6685fde43d21
604 613 tag: tip
605 614 user: test
606 615 date: * (glob)
607 616 summary: blah
608 617 files:
609 618 M a
610 619 b
611 620 R b
612 621
613 622
614 623 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now