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