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