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