##// END OF EJS Templates
config: also respect HGRCSKIPREPO in `dispatch._getlocal`...
marmoute -
r44728:ef11dfc5 stable
parent child Browse files
Show More
@@ -1,1282 +1,1286 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import difflib
11 11 import errno
12 12 import getopt
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import traceback
19 19
20 20
21 21 from .i18n import _
22 22 from .pycompat import getattr
23 23
24 24 from hgdemandimport import tracing
25 25
26 26 from . import (
27 27 cmdutil,
28 28 color,
29 29 commands,
30 30 demandimport,
31 31 encoding,
32 32 error,
33 33 extensions,
34 34 fancyopts,
35 35 help,
36 36 hg,
37 37 hook,
38 38 profiling,
39 39 pycompat,
40 rcutil,
40 41 registrar,
41 42 scmutil,
42 43 ui as uimod,
43 44 util,
44 45 )
45 46
46 47 from .utils import (
47 48 procutil,
48 49 stringutil,
49 50 )
50 51
51 52
52 53 class request(object):
53 54 def __init__(
54 55 self,
55 56 args,
56 57 ui=None,
57 58 repo=None,
58 59 fin=None,
59 60 fout=None,
60 61 ferr=None,
61 62 fmsg=None,
62 63 prereposetups=None,
63 64 ):
64 65 self.args = args
65 66 self.ui = ui
66 67 self.repo = repo
67 68
68 69 # input/output/error streams
69 70 self.fin = fin
70 71 self.fout = fout
71 72 self.ferr = ferr
72 73 # separate stream for status/error messages
73 74 self.fmsg = fmsg
74 75
75 76 # remember options pre-parsed by _earlyparseopts()
76 77 self.earlyoptions = {}
77 78
78 79 # reposetups which run before extensions, useful for chg to pre-fill
79 80 # low-level repo state (for example, changelog) before extensions.
80 81 self.prereposetups = prereposetups or []
81 82
82 83 # store the parsed and canonical command
83 84 self.canonical_command = None
84 85
85 86 def _runexithandlers(self):
86 87 exc = None
87 88 handlers = self.ui._exithandlers
88 89 try:
89 90 while handlers:
90 91 func, args, kwargs = handlers.pop()
91 92 try:
92 93 func(*args, **kwargs)
93 94 except: # re-raises below
94 95 if exc is None:
95 96 exc = sys.exc_info()[1]
96 97 self.ui.warnnoi18n(b'error in exit handlers:\n')
97 98 self.ui.traceback(force=True)
98 99 finally:
99 100 if exc is not None:
100 101 raise exc
101 102
102 103
103 104 def run():
104 105 """run the command in sys.argv"""
105 106 initstdio()
106 107 with tracing.log('parse args into request'):
107 108 req = request(pycompat.sysargv[1:])
108 109 err = None
109 110 try:
110 111 status = dispatch(req)
111 112 except error.StdioError as e:
112 113 err = e
113 114 status = -1
114 115
115 116 # In all cases we try to flush stdio streams.
116 117 if util.safehasattr(req.ui, b'fout'):
117 118 assert req.ui is not None # help pytype
118 119 assert req.ui.fout is not None # help pytype
119 120 try:
120 121 req.ui.fout.flush()
121 122 except IOError as e:
122 123 err = e
123 124 status = -1
124 125
125 126 if util.safehasattr(req.ui, b'ferr'):
126 127 assert req.ui is not None # help pytype
127 128 assert req.ui.ferr is not None # help pytype
128 129 try:
129 130 if err is not None and err.errno != errno.EPIPE:
130 131 req.ui.ferr.write(
131 132 b'abort: %s\n' % encoding.strtolocal(err.strerror)
132 133 )
133 134 req.ui.ferr.flush()
134 135 # There's not much we can do about an I/O error here. So (possibly)
135 136 # change the status code and move on.
136 137 except IOError:
137 138 status = -1
138 139
139 140 _silencestdio()
140 141 sys.exit(status & 255)
141 142
142 143
143 144 if pycompat.ispy3:
144 145
145 146 def initstdio():
146 147 pass
147 148
148 149 def _silencestdio():
149 150 for fp in (sys.stdout, sys.stderr):
150 151 # Check if the file is okay
151 152 try:
152 153 fp.flush()
153 154 continue
154 155 except IOError:
155 156 pass
156 157 # Otherwise mark it as closed to silence "Exception ignored in"
157 158 # message emitted by the interpreter finalizer. Be careful to
158 159 # not close procutil.stdout, which may be a fdopen-ed file object
159 160 # and its close() actually closes the underlying file descriptor.
160 161 try:
161 162 fp.close()
162 163 except IOError:
163 164 pass
164 165
165 166
166 167 else:
167 168
168 169 def initstdio():
169 170 for fp in (sys.stdin, sys.stdout, sys.stderr):
170 171 procutil.setbinary(fp)
171 172
172 173 def _silencestdio():
173 174 pass
174 175
175 176
176 177 def _getsimilar(symbols, value):
177 178 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
178 179 # The cutoff for similarity here is pretty arbitrary. It should
179 180 # probably be investigated and tweaked.
180 181 return [s for s in symbols if sim(s) > 0.6]
181 182
182 183
183 184 def _reportsimilar(write, similar):
184 185 if len(similar) == 1:
185 186 write(_(b"(did you mean %s?)\n") % similar[0])
186 187 elif similar:
187 188 ss = b", ".join(sorted(similar))
188 189 write(_(b"(did you mean one of %s?)\n") % ss)
189 190
190 191
191 192 def _formatparse(write, inst):
192 193 similar = []
193 194 if isinstance(inst, error.UnknownIdentifier):
194 195 # make sure to check fileset first, as revset can invoke fileset
195 196 similar = _getsimilar(inst.symbols, inst.function)
196 197 if len(inst.args) > 1:
197 198 write(
198 199 _(b"hg: parse error at %s: %s\n")
199 200 % (pycompat.bytestr(inst.args[1]), inst.args[0])
200 201 )
201 202 if inst.args[0].startswith(b' '):
202 203 write(_(b"unexpected leading whitespace\n"))
203 204 else:
204 205 write(_(b"hg: parse error: %s\n") % inst.args[0])
205 206 _reportsimilar(write, similar)
206 207 if inst.hint:
207 208 write(_(b"(%s)\n") % inst.hint)
208 209
209 210
210 211 def _formatargs(args):
211 212 return b' '.join(procutil.shellquote(a) for a in args)
212 213
213 214
214 215 def dispatch(req):
215 216 """run the command specified in req.args; returns an integer status code"""
216 217 with tracing.log('dispatch.dispatch'):
217 218 if req.ferr:
218 219 ferr = req.ferr
219 220 elif req.ui:
220 221 ferr = req.ui.ferr
221 222 else:
222 223 ferr = procutil.stderr
223 224
224 225 try:
225 226 if not req.ui:
226 227 req.ui = uimod.ui.load()
227 228 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
228 229 if req.earlyoptions[b'traceback']:
229 230 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
230 231
231 232 # set ui streams from the request
232 233 if req.fin:
233 234 req.ui.fin = req.fin
234 235 if req.fout:
235 236 req.ui.fout = req.fout
236 237 if req.ferr:
237 238 req.ui.ferr = req.ferr
238 239 if req.fmsg:
239 240 req.ui.fmsg = req.fmsg
240 241 except error.Abort as inst:
241 242 ferr.write(_(b"abort: %s\n") % inst)
242 243 if inst.hint:
243 244 ferr.write(_(b"(%s)\n") % inst.hint)
244 245 return -1
245 246 except error.ParseError as inst:
246 247 _formatparse(ferr.write, inst)
247 248 return -1
248 249
249 250 msg = _formatargs(req.args)
250 251 starttime = util.timer()
251 252 ret = 1 # default of Python exit code on unhandled exception
252 253 try:
253 254 ret = _runcatch(req) or 0
254 255 except error.ProgrammingError as inst:
255 256 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
256 257 if inst.hint:
257 258 req.ui.error(_(b'** (%s)\n') % inst.hint)
258 259 raise
259 260 except KeyboardInterrupt as inst:
260 261 try:
261 262 if isinstance(inst, error.SignalInterrupt):
262 263 msg = _(b"killed!\n")
263 264 else:
264 265 msg = _(b"interrupted!\n")
265 266 req.ui.error(msg)
266 267 except error.SignalInterrupt:
267 268 # maybe pager would quit without consuming all the output, and
268 269 # SIGPIPE was raised. we cannot print anything in this case.
269 270 pass
270 271 except IOError as inst:
271 272 if inst.errno != errno.EPIPE:
272 273 raise
273 274 ret = -1
274 275 finally:
275 276 duration = util.timer() - starttime
276 277 req.ui.flush()
277 278 if req.ui.logblockedtimes:
278 279 req.ui._blockedtimes[b'command_duration'] = duration * 1000
279 280 req.ui.log(
280 281 b'uiblocked',
281 282 b'ui blocked ms\n',
282 283 **pycompat.strkwargs(req.ui._blockedtimes)
283 284 )
284 285 return_code = ret & 255
285 286 req.ui.log(
286 287 b"commandfinish",
287 288 b"%s exited %d after %0.2f seconds\n",
288 289 msg,
289 290 return_code,
290 291 duration,
291 292 return_code=return_code,
292 293 duration=duration,
293 294 canonical_command=req.canonical_command,
294 295 )
295 296 try:
296 297 req._runexithandlers()
297 298 except: # exiting, so no re-raises
298 299 ret = ret or -1
299 300 return ret
300 301
301 302
302 303 def _runcatch(req):
303 304 with tracing.log('dispatch._runcatch'):
304 305
305 306 def catchterm(*args):
306 307 raise error.SignalInterrupt
307 308
308 309 ui = req.ui
309 310 try:
310 311 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
311 312 num = getattr(signal, name, None)
312 313 if num:
313 314 signal.signal(num, catchterm)
314 315 except ValueError:
315 316 pass # happens if called in a thread
316 317
317 318 def _runcatchfunc():
318 319 realcmd = None
319 320 try:
320 321 cmdargs = fancyopts.fancyopts(
321 322 req.args[:], commands.globalopts, {}
322 323 )
323 324 cmd = cmdargs[0]
324 325 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
325 326 realcmd = aliases[0]
326 327 except (
327 328 error.UnknownCommand,
328 329 error.AmbiguousCommand,
329 330 IndexError,
330 331 getopt.GetoptError,
331 332 ):
332 333 # Don't handle this here. We know the command is
333 334 # invalid, but all we're worried about for now is that
334 335 # it's not a command that server operators expect to
335 336 # be safe to offer to users in a sandbox.
336 337 pass
337 338 if realcmd == b'serve' and b'--stdio' in cmdargs:
338 339 # We want to constrain 'hg serve --stdio' instances pretty
339 340 # closely, as many shared-ssh access tools want to grant
340 341 # access to run *only* 'hg -R $repo serve --stdio'. We
341 342 # restrict to exactly that set of arguments, and prohibit
342 343 # any repo name that starts with '--' to prevent
343 344 # shenanigans wherein a user does something like pass
344 345 # --debugger or --config=ui.debugger=1 as a repo
345 346 # name. This used to actually run the debugger.
346 347 if (
347 348 len(req.args) != 4
348 349 or req.args[0] != b'-R'
349 350 or req.args[1].startswith(b'--')
350 351 or req.args[2] != b'serve'
351 352 or req.args[3] != b'--stdio'
352 353 ):
353 354 raise error.Abort(
354 355 _(b'potentially unsafe serve --stdio invocation: %s')
355 356 % (stringutil.pprint(req.args),)
356 357 )
357 358
358 359 try:
359 360 debugger = b'pdb'
360 361 debugtrace = {b'pdb': pdb.set_trace}
361 362 debugmortem = {b'pdb': pdb.post_mortem}
362 363
363 364 # read --config before doing anything else
364 365 # (e.g. to change trust settings for reading .hg/hgrc)
365 366 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
366 367
367 368 if req.repo:
368 369 # copy configs that were passed on the cmdline (--config) to
369 370 # the repo ui
370 371 for sec, name, val in cfgs:
371 372 req.repo.ui.setconfig(
372 373 sec, name, val, source=b'--config'
373 374 )
374 375
375 376 # developer config: ui.debugger
376 377 debugger = ui.config(b"ui", b"debugger")
377 378 debugmod = pdb
378 379 if not debugger or ui.plain():
379 380 # if we are in HGPLAIN mode, then disable custom debugging
380 381 debugger = b'pdb'
381 382 elif req.earlyoptions[b'debugger']:
382 383 # This import can be slow for fancy debuggers, so only
383 384 # do it when absolutely necessary, i.e. when actual
384 385 # debugging has been requested
385 386 with demandimport.deactivated():
386 387 try:
387 388 debugmod = __import__(debugger)
388 389 except ImportError:
389 390 pass # Leave debugmod = pdb
390 391
391 392 debugtrace[debugger] = debugmod.set_trace
392 393 debugmortem[debugger] = debugmod.post_mortem
393 394
394 395 # enter the debugger before command execution
395 396 if req.earlyoptions[b'debugger']:
396 397 ui.warn(
397 398 _(
398 399 b"entering debugger - "
399 400 b"type c to continue starting hg or h for help\n"
400 401 )
401 402 )
402 403
403 404 if (
404 405 debugger != b'pdb'
405 406 and debugtrace[debugger] == debugtrace[b'pdb']
406 407 ):
407 408 ui.warn(
408 409 _(
409 410 b"%s debugger specified "
410 411 b"but its module was not found\n"
411 412 )
412 413 % debugger
413 414 )
414 415 with demandimport.deactivated():
415 416 debugtrace[debugger]()
416 417 try:
417 418 return _dispatch(req)
418 419 finally:
419 420 ui.flush()
420 421 except: # re-raises
421 422 # enter the debugger when we hit an exception
422 423 if req.earlyoptions[b'debugger']:
423 424 traceback.print_exc()
424 425 debugmortem[debugger](sys.exc_info()[2])
425 426 raise
426 427
427 428 return _callcatch(ui, _runcatchfunc)
428 429
429 430
430 431 def _callcatch(ui, func):
431 432 """like scmutil.callcatch but handles more high-level exceptions about
432 433 config parsing and commands. besides, use handlecommandexception to handle
433 434 uncaught exceptions.
434 435 """
435 436 try:
436 437 return scmutil.callcatch(ui, func)
437 438 except error.AmbiguousCommand as inst:
438 439 ui.warn(
439 440 _(b"hg: command '%s' is ambiguous:\n %s\n")
440 441 % (inst.args[0], b" ".join(inst.args[1]))
441 442 )
442 443 except error.CommandError as inst:
443 444 if inst.args[0]:
444 445 ui.pager(b'help')
445 446 msgbytes = pycompat.bytestr(inst.args[1])
446 447 ui.warn(_(b"hg %s: %s\n") % (inst.args[0], msgbytes))
447 448 commands.help_(ui, inst.args[0], full=False, command=True)
448 449 else:
449 450 ui.warn(_(b"hg: %s\n") % inst.args[1])
450 451 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
451 452 except error.ParseError as inst:
452 453 _formatparse(ui.warn, inst)
453 454 return -1
454 455 except error.UnknownCommand as inst:
455 456 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.args[0]
456 457 try:
457 458 # check if the command is in a disabled extension
458 459 # (but don't check for extensions themselves)
459 460 formatted = help.formattedhelp(
460 461 ui, commands, inst.args[0], unknowncmd=True
461 462 )
462 463 ui.warn(nocmdmsg)
463 464 ui.write(formatted)
464 465 except (error.UnknownCommand, error.Abort):
465 466 suggested = False
466 467 if len(inst.args) == 2:
467 468 sim = _getsimilar(inst.args[1], inst.args[0])
468 469 if sim:
469 470 ui.warn(nocmdmsg)
470 471 _reportsimilar(ui.warn, sim)
471 472 suggested = True
472 473 if not suggested:
473 474 ui.warn(nocmdmsg)
474 475 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
475 476 except IOError:
476 477 raise
477 478 except KeyboardInterrupt:
478 479 raise
479 480 except: # probably re-raises
480 481 if not handlecommandexception(ui):
481 482 raise
482 483
483 484 return -1
484 485
485 486
486 487 def aliasargs(fn, givenargs):
487 488 args = []
488 489 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
489 490 if not util.safehasattr(fn, b'_origfunc'):
490 491 args = getattr(fn, 'args', args)
491 492 if args:
492 493 cmd = b' '.join(map(procutil.shellquote, args))
493 494
494 495 nums = []
495 496
496 497 def replacer(m):
497 498 num = int(m.group(1)) - 1
498 499 nums.append(num)
499 500 if num < len(givenargs):
500 501 return givenargs[num]
501 502 raise error.Abort(_(b'too few arguments for command alias'))
502 503
503 504 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
504 505 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
505 506 args = pycompat.shlexsplit(cmd)
506 507 return args + givenargs
507 508
508 509
509 510 def aliasinterpolate(name, args, cmd):
510 511 '''interpolate args into cmd for shell aliases
511 512
512 513 This also handles $0, $@ and "$@".
513 514 '''
514 515 # util.interpolate can't deal with "$@" (with quotes) because it's only
515 516 # built to match prefix + patterns.
516 517 replacemap = dict((b'$%d' % (i + 1), arg) for i, arg in enumerate(args))
517 518 replacemap[b'$0'] = name
518 519 replacemap[b'$$'] = b'$'
519 520 replacemap[b'$@'] = b' '.join(args)
520 521 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
521 522 # parameters, separated out into words. Emulate the same behavior here by
522 523 # quoting the arguments individually. POSIX shells will then typically
523 524 # tokenize each argument into exactly one word.
524 525 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
525 526 # escape '\$' for regex
526 527 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
527 528 r = re.compile(regex)
528 529 return r.sub(lambda x: replacemap[x.group()], cmd)
529 530
530 531
531 532 class cmdalias(object):
532 533 def __init__(self, ui, name, definition, cmdtable, source):
533 534 self.name = self.cmd = name
534 535 self.cmdname = b''
535 536 self.definition = definition
536 537 self.fn = None
537 538 self.givenargs = []
538 539 self.opts = []
539 540 self.help = b''
540 541 self.badalias = None
541 542 self.unknowncmd = False
542 543 self.source = source
543 544
544 545 try:
545 546 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
546 547 for alias, e in pycompat.iteritems(cmdtable):
547 548 if e is entry:
548 549 self.cmd = alias
549 550 break
550 551 self.shadows = True
551 552 except error.UnknownCommand:
552 553 self.shadows = False
553 554
554 555 if not self.definition:
555 556 self.badalias = _(b"no definition for alias '%s'") % self.name
556 557 return
557 558
558 559 if self.definition.startswith(b'!'):
559 560 shdef = self.definition[1:]
560 561 self.shell = True
561 562
562 563 def fn(ui, *args):
563 564 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
564 565
565 566 def _checkvar(m):
566 567 if m.groups()[0] == b'$':
567 568 return m.group()
568 569 elif int(m.groups()[0]) <= len(args):
569 570 return m.group()
570 571 else:
571 572 ui.debug(
572 573 b"No argument found for substitution "
573 574 b"of %i variable in alias '%s' definition.\n"
574 575 % (int(m.groups()[0]), self.name)
575 576 )
576 577 return b''
577 578
578 579 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
579 580 cmd = aliasinterpolate(self.name, args, cmd)
580 581 return ui.system(
581 582 cmd, environ=env, blockedtag=b'alias_%s' % self.name
582 583 )
583 584
584 585 self.fn = fn
585 586 self.alias = True
586 587 self._populatehelp(ui, name, shdef, self.fn)
587 588 return
588 589
589 590 try:
590 591 args = pycompat.shlexsplit(self.definition)
591 592 except ValueError as inst:
592 593 self.badalias = _(b"error in definition for alias '%s': %s") % (
593 594 self.name,
594 595 stringutil.forcebytestr(inst),
595 596 )
596 597 return
597 598 earlyopts, args = _earlysplitopts(args)
598 599 if earlyopts:
599 600 self.badalias = _(
600 601 b"error in definition for alias '%s': %s may "
601 602 b"only be given on the command line"
602 603 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
603 604 return
604 605 self.cmdname = cmd = args.pop(0)
605 606 self.givenargs = args
606 607
607 608 try:
608 609 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
609 610 if len(tableentry) > 2:
610 611 self.fn, self.opts, cmdhelp = tableentry
611 612 else:
612 613 self.fn, self.opts = tableentry
613 614 cmdhelp = None
614 615
615 616 self.alias = True
616 617 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
617 618
618 619 except error.UnknownCommand:
619 620 self.badalias = _(
620 621 b"alias '%s' resolves to unknown command '%s'"
621 622 ) % (self.name, cmd,)
622 623 self.unknowncmd = True
623 624 except error.AmbiguousCommand:
624 625 self.badalias = _(
625 626 b"alias '%s' resolves to ambiguous command '%s'"
626 627 ) % (self.name, cmd)
627 628
628 629 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
629 630 # confine strings to be passed to i18n.gettext()
630 631 cfg = {}
631 632 for k in (b'doc', b'help', b'category'):
632 633 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
633 634 if v is None:
634 635 continue
635 636 if not encoding.isasciistr(v):
636 637 self.badalias = _(
637 638 b"non-ASCII character in alias definition '%s:%s'"
638 639 ) % (name, k)
639 640 return
640 641 cfg[k] = v
641 642
642 643 self.help = cfg.get(b'help', defaulthelp or b'')
643 644 if self.help and self.help.startswith(b"hg " + cmd):
644 645 # drop prefix in old-style help lines so hg shows the alias
645 646 self.help = self.help[4 + len(cmd) :]
646 647
647 648 self.owndoc = b'doc' in cfg
648 649 doc = cfg.get(b'doc', pycompat.getdoc(fn))
649 650 if doc is not None:
650 651 doc = pycompat.sysstr(doc)
651 652 self.__doc__ = doc
652 653
653 654 self.helpcategory = cfg.get(
654 655 b'category', registrar.command.CATEGORY_NONE
655 656 )
656 657
657 658 @property
658 659 def args(self):
659 660 args = pycompat.maplist(util.expandpath, self.givenargs)
660 661 return aliasargs(self.fn, args)
661 662
662 663 def __getattr__(self, name):
663 664 adefaults = {
664 665 'norepo': True,
665 666 'intents': set(),
666 667 'optionalrepo': False,
667 668 'inferrepo': False,
668 669 }
669 670 if name not in adefaults:
670 671 raise AttributeError(name)
671 672 if self.badalias or util.safehasattr(self, b'shell'):
672 673 return adefaults[name]
673 674 return getattr(self.fn, name)
674 675
675 676 def __call__(self, ui, *args, **opts):
676 677 if self.badalias:
677 678 hint = None
678 679 if self.unknowncmd:
679 680 try:
680 681 # check if the command is in a disabled extension
681 682 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
682 683 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
683 684 except error.UnknownCommand:
684 685 pass
685 686 raise error.Abort(self.badalias, hint=hint)
686 687 if self.shadows:
687 688 ui.debug(
688 689 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
689 690 )
690 691
691 692 ui.log(
692 693 b'commandalias',
693 694 b"alias '%s' expands to '%s'\n",
694 695 self.name,
695 696 self.definition,
696 697 )
697 698 if util.safehasattr(self, b'shell'):
698 699 return self.fn(ui, *args, **opts)
699 700 else:
700 701 try:
701 702 return util.checksignature(self.fn)(ui, *args, **opts)
702 703 except error.SignatureError:
703 704 args = b' '.join([self.cmdname] + self.args)
704 705 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
705 706 raise
706 707
707 708
708 709 class lazyaliasentry(object):
709 710 """like a typical command entry (func, opts, help), but is lazy"""
710 711
711 712 def __init__(self, ui, name, definition, cmdtable, source):
712 713 self.ui = ui
713 714 self.name = name
714 715 self.definition = definition
715 716 self.cmdtable = cmdtable.copy()
716 717 self.source = source
717 718 self.alias = True
718 719
719 720 @util.propertycache
720 721 def _aliasdef(self):
721 722 return cmdalias(
722 723 self.ui, self.name, self.definition, self.cmdtable, self.source
723 724 )
724 725
725 726 def __getitem__(self, n):
726 727 aliasdef = self._aliasdef
727 728 if n == 0:
728 729 return aliasdef
729 730 elif n == 1:
730 731 return aliasdef.opts
731 732 elif n == 2:
732 733 return aliasdef.help
733 734 else:
734 735 raise IndexError
735 736
736 737 def __iter__(self):
737 738 for i in range(3):
738 739 yield self[i]
739 740
740 741 def __len__(self):
741 742 return 3
742 743
743 744
744 745 def addaliases(ui, cmdtable):
745 746 # aliases are processed after extensions have been loaded, so they
746 747 # may use extension commands. Aliases can also use other alias definitions,
747 748 # but only if they have been defined prior to the current definition.
748 749 for alias, definition in ui.configitems(b'alias', ignoresub=True):
749 750 try:
750 751 if cmdtable[alias].definition == definition:
751 752 continue
752 753 except (KeyError, AttributeError):
753 754 # definition might not exist or it might not be a cmdalias
754 755 pass
755 756
756 757 source = ui.configsource(b'alias', alias)
757 758 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
758 759 cmdtable[alias] = entry
759 760
760 761
761 762 def _parse(ui, args):
762 763 options = {}
763 764 cmdoptions = {}
764 765
765 766 try:
766 767 args = fancyopts.fancyopts(args, commands.globalopts, options)
767 768 except getopt.GetoptError as inst:
768 769 raise error.CommandError(None, stringutil.forcebytestr(inst))
769 770
770 771 if args:
771 772 cmd, args = args[0], args[1:]
772 773 aliases, entry = cmdutil.findcmd(
773 774 cmd, commands.table, ui.configbool(b"ui", b"strict")
774 775 )
775 776 cmd = aliases[0]
776 777 args = aliasargs(entry[0], args)
777 778 defaults = ui.config(b"defaults", cmd)
778 779 if defaults:
779 780 args = (
780 781 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
781 782 + args
782 783 )
783 784 c = list(entry[1])
784 785 else:
785 786 cmd = None
786 787 c = []
787 788
788 789 # combine global options into local
789 790 for o in commands.globalopts:
790 791 c.append((o[0], o[1], options[o[1]], o[3]))
791 792
792 793 try:
793 794 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
794 795 except getopt.GetoptError as inst:
795 796 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
796 797
797 798 # separate global options back out
798 799 for o in commands.globalopts:
799 800 n = o[1]
800 801 options[n] = cmdoptions[n]
801 802 del cmdoptions[n]
802 803
803 804 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
804 805
805 806
806 807 def _parseconfig(ui, config):
807 808 """parse the --config options from the command line"""
808 809 configs = []
809 810
810 811 for cfg in config:
811 812 try:
812 813 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
813 814 section, name = name.split(b'.', 1)
814 815 if not section or not name:
815 816 raise IndexError
816 817 ui.setconfig(section, name, value, b'--config')
817 818 configs.append((section, name, value))
818 819 except (IndexError, ValueError):
819 820 raise error.Abort(
820 821 _(
821 822 b'malformed --config option: %r '
822 823 b'(use --config section.name=value)'
823 824 )
824 825 % pycompat.bytestr(cfg)
825 826 )
826 827
827 828 return configs
828 829
829 830
830 831 def _earlyparseopts(ui, args):
831 832 options = {}
832 833 fancyopts.fancyopts(
833 834 args,
834 835 commands.globalopts,
835 836 options,
836 837 gnu=not ui.plain(b'strictflags'),
837 838 early=True,
838 839 optaliases={b'repository': [b'repo']},
839 840 )
840 841 return options
841 842
842 843
843 844 def _earlysplitopts(args):
844 845 """Split args into a list of possible early options and remainder args"""
845 846 shortoptions = b'R:'
846 847 # TODO: perhaps 'debugger' should be included
847 848 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
848 849 return fancyopts.earlygetopt(
849 850 args, shortoptions, longoptions, gnu=True, keepsep=True
850 851 )
851 852
852 853
853 854 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
854 855 # run pre-hook, and abort if it fails
855 856 hook.hook(
856 857 lui,
857 858 repo,
858 859 b"pre-%s" % cmd,
859 860 True,
860 861 args=b" ".join(fullargs),
861 862 pats=cmdpats,
862 863 opts=cmdoptions,
863 864 )
864 865 try:
865 866 ret = _runcommand(ui, options, cmd, d)
866 867 # run post-hook, passing command result
867 868 hook.hook(
868 869 lui,
869 870 repo,
870 871 b"post-%s" % cmd,
871 872 False,
872 873 args=b" ".join(fullargs),
873 874 result=ret,
874 875 pats=cmdpats,
875 876 opts=cmdoptions,
876 877 )
877 878 except Exception:
878 879 # run failure hook and re-raise
879 880 hook.hook(
880 881 lui,
881 882 repo,
882 883 b"fail-%s" % cmd,
883 884 False,
884 885 args=b" ".join(fullargs),
885 886 pats=cmdpats,
886 887 opts=cmdoptions,
887 888 )
888 889 raise
889 890 return ret
890 891
891 892
892 893 def _getlocal(ui, rpath, wd=None):
893 894 """Return (path, local ui object) for the given target path.
894 895
895 896 Takes paths in [cwd]/.hg/hgrc into account."
896 897 """
897 898 if wd is None:
898 899 try:
899 900 wd = encoding.getcwd()
900 901 except OSError as e:
901 902 raise error.Abort(
902 903 _(b"error getting current working directory: %s")
903 904 % encoding.strtolocal(e.strerror)
904 905 )
906
905 907 path = cmdutil.findrepo(wd) or b""
906 908 if not path:
907 909 lui = ui
908 910 else:
909 911 lui = ui.copy()
912 if rcutil.use_repo_hgrc():
910 913 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
911 914
912 915 if rpath:
913 916 path = lui.expandpath(rpath)
914 917 lui = ui.copy()
918 if rcutil.use_repo_hgrc():
915 919 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
916 920
917 921 return path, lui
918 922
919 923
920 924 def _checkshellalias(lui, ui, args):
921 925 """Return the function to run the shell alias, if it is required"""
922 926 options = {}
923 927
924 928 try:
925 929 args = fancyopts.fancyopts(args, commands.globalopts, options)
926 930 except getopt.GetoptError:
927 931 return
928 932
929 933 if not args:
930 934 return
931 935
932 936 cmdtable = commands.table
933 937
934 938 cmd = args[0]
935 939 try:
936 940 strict = ui.configbool(b"ui", b"strict")
937 941 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
938 942 except (error.AmbiguousCommand, error.UnknownCommand):
939 943 return
940 944
941 945 cmd = aliases[0]
942 946 fn = entry[0]
943 947
944 948 if cmd and util.safehasattr(fn, b'shell'):
945 949 # shell alias shouldn't receive early options which are consumed by hg
946 950 _earlyopts, args = _earlysplitopts(args)
947 951 d = lambda: fn(ui, *args[1:])
948 952 return lambda: runcommand(
949 953 lui, None, cmd, args[:1], ui, options, d, [], {}
950 954 )
951 955
952 956
953 957 def _dispatch(req):
954 958 args = req.args
955 959 ui = req.ui
956 960
957 961 # check for cwd
958 962 cwd = req.earlyoptions[b'cwd']
959 963 if cwd:
960 964 os.chdir(cwd)
961 965
962 966 rpath = req.earlyoptions[b'repository']
963 967 path, lui = _getlocal(ui, rpath)
964 968
965 969 uis = {ui, lui}
966 970
967 971 if req.repo:
968 972 uis.add(req.repo.ui)
969 973
970 974 if (
971 975 req.earlyoptions[b'verbose']
972 976 or req.earlyoptions[b'debug']
973 977 or req.earlyoptions[b'quiet']
974 978 ):
975 979 for opt in (b'verbose', b'debug', b'quiet'):
976 980 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
977 981 for ui_ in uis:
978 982 ui_.setconfig(b'ui', opt, val, b'--' + opt)
979 983
980 984 if req.earlyoptions[b'profile']:
981 985 for ui_ in uis:
982 986 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
983 987
984 988 profile = lui.configbool(b'profiling', b'enabled')
985 989 with profiling.profile(lui, enabled=profile) as profiler:
986 990 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
987 991 # reposetup
988 992 extensions.loadall(lui)
989 993 # Propagate any changes to lui.__class__ by extensions
990 994 ui.__class__ = lui.__class__
991 995
992 996 # (uisetup and extsetup are handled in extensions.loadall)
993 997
994 998 # (reposetup is handled in hg.repository)
995 999
996 1000 addaliases(lui, commands.table)
997 1001
998 1002 # All aliases and commands are completely defined, now.
999 1003 # Check abbreviation/ambiguity of shell alias.
1000 1004 shellaliasfn = _checkshellalias(lui, ui, args)
1001 1005 if shellaliasfn:
1002 1006 # no additional configs will be set, set up the ui instances
1003 1007 for ui_ in uis:
1004 1008 extensions.populateui(ui_)
1005 1009 return shellaliasfn()
1006 1010
1007 1011 # check for fallback encoding
1008 1012 fallback = lui.config(b'ui', b'fallbackencoding')
1009 1013 if fallback:
1010 1014 encoding.fallbackencoding = fallback
1011 1015
1012 1016 fullargs = args
1013 1017 cmd, func, args, options, cmdoptions = _parse(lui, args)
1014 1018
1015 1019 # store the canonical command name in request object for later access
1016 1020 req.canonical_command = cmd
1017 1021
1018 1022 if options[b"config"] != req.earlyoptions[b"config"]:
1019 1023 raise error.Abort(_(b"option --config may not be abbreviated!"))
1020 1024 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1021 1025 raise error.Abort(_(b"option --cwd may not be abbreviated!"))
1022 1026 if options[b"repository"] != req.earlyoptions[b"repository"]:
1023 1027 raise error.Abort(
1024 1028 _(
1025 1029 b"option -R has to be separated from other options (e.g. not "
1026 1030 b"-qR) and --repository may only be abbreviated as --repo!"
1027 1031 )
1028 1032 )
1029 1033 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1030 1034 raise error.Abort(_(b"option --debugger may not be abbreviated!"))
1031 1035 # don't validate --profile/--traceback, which can be enabled from now
1032 1036
1033 1037 if options[b"encoding"]:
1034 1038 encoding.encoding = options[b"encoding"]
1035 1039 if options[b"encodingmode"]:
1036 1040 encoding.encodingmode = options[b"encodingmode"]
1037 1041 if options[b"time"]:
1038 1042
1039 1043 def get_times():
1040 1044 t = os.times()
1041 1045 if t[4] == 0.0:
1042 1046 # Windows leaves this as zero, so use time.perf_counter()
1043 1047 t = (t[0], t[1], t[2], t[3], util.timer())
1044 1048 return t
1045 1049
1046 1050 s = get_times()
1047 1051
1048 1052 def print_time():
1049 1053 t = get_times()
1050 1054 ui.warn(
1051 1055 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1052 1056 % (
1053 1057 t[4] - s[4],
1054 1058 t[0] - s[0],
1055 1059 t[2] - s[2],
1056 1060 t[1] - s[1],
1057 1061 t[3] - s[3],
1058 1062 )
1059 1063 )
1060 1064
1061 1065 ui.atexit(print_time)
1062 1066 if options[b"profile"]:
1063 1067 profiler.start()
1064 1068
1065 1069 # if abbreviated version of this were used, take them in account, now
1066 1070 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1067 1071 for opt in (b'verbose', b'debug', b'quiet'):
1068 1072 if options[opt] == req.earlyoptions[opt]:
1069 1073 continue
1070 1074 val = pycompat.bytestr(bool(options[opt]))
1071 1075 for ui_ in uis:
1072 1076 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1073 1077
1074 1078 if options[b'traceback']:
1075 1079 for ui_ in uis:
1076 1080 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1077 1081
1078 1082 if options[b'noninteractive']:
1079 1083 for ui_ in uis:
1080 1084 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1081 1085
1082 1086 if cmdoptions.get(b'insecure', False):
1083 1087 for ui_ in uis:
1084 1088 ui_.insecureconnections = True
1085 1089
1086 1090 # setup color handling before pager, because setting up pager
1087 1091 # might cause incorrect console information
1088 1092 coloropt = options[b'color']
1089 1093 for ui_ in uis:
1090 1094 if coloropt:
1091 1095 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1092 1096 color.setup(ui_)
1093 1097
1094 1098 if stringutil.parsebool(options[b'pager']):
1095 1099 # ui.pager() expects 'internal-always-' prefix in this case
1096 1100 ui.pager(b'internal-always-' + cmd)
1097 1101 elif options[b'pager'] != b'auto':
1098 1102 for ui_ in uis:
1099 1103 ui_.disablepager()
1100 1104
1101 1105 # configs are fully loaded, set up the ui instances
1102 1106 for ui_ in uis:
1103 1107 extensions.populateui(ui_)
1104 1108
1105 1109 if options[b'version']:
1106 1110 return commands.version_(ui)
1107 1111 if options[b'help']:
1108 1112 return commands.help_(ui, cmd, command=cmd is not None)
1109 1113 elif not cmd:
1110 1114 return commands.help_(ui, b'shortlist')
1111 1115
1112 1116 repo = None
1113 1117 cmdpats = args[:]
1114 1118 assert func is not None # help out pytype
1115 1119 if not func.norepo:
1116 1120 # use the repo from the request only if we don't have -R
1117 1121 if not rpath and not cwd:
1118 1122 repo = req.repo
1119 1123
1120 1124 if repo:
1121 1125 # set the descriptors of the repo ui to those of ui
1122 1126 repo.ui.fin = ui.fin
1123 1127 repo.ui.fout = ui.fout
1124 1128 repo.ui.ferr = ui.ferr
1125 1129 repo.ui.fmsg = ui.fmsg
1126 1130 else:
1127 1131 try:
1128 1132 repo = hg.repository(
1129 1133 ui,
1130 1134 path=path,
1131 1135 presetupfuncs=req.prereposetups,
1132 1136 intents=func.intents,
1133 1137 )
1134 1138 if not repo.local():
1135 1139 raise error.Abort(
1136 1140 _(b"repository '%s' is not local") % path
1137 1141 )
1138 1142 repo.ui.setconfig(
1139 1143 b"bundle", b"mainreporoot", repo.root, b'repo'
1140 1144 )
1141 1145 except error.RequirementError:
1142 1146 raise
1143 1147 except error.RepoError:
1144 1148 if rpath: # invalid -R path
1145 1149 raise
1146 1150 if not func.optionalrepo:
1147 1151 if func.inferrepo and args and not path:
1148 1152 # try to infer -R from command args
1149 1153 repos = pycompat.maplist(cmdutil.findrepo, args)
1150 1154 guess = repos[0]
1151 1155 if guess and repos.count(guess) == len(repos):
1152 1156 req.args = [b'--repository', guess] + fullargs
1153 1157 req.earlyoptions[b'repository'] = guess
1154 1158 return _dispatch(req)
1155 1159 if not path:
1156 1160 raise error.RepoError(
1157 1161 _(
1158 1162 b"no repository found in"
1159 1163 b" '%s' (.hg not found)"
1160 1164 )
1161 1165 % encoding.getcwd()
1162 1166 )
1163 1167 raise
1164 1168 if repo:
1165 1169 ui = repo.ui
1166 1170 if options[b'hidden']:
1167 1171 repo = repo.unfiltered()
1168 1172 args.insert(0, repo)
1169 1173 elif rpath:
1170 1174 ui.warn(_(b"warning: --repository ignored\n"))
1171 1175
1172 1176 msg = _formatargs(fullargs)
1173 1177 ui.log(b"command", b'%s\n', msg)
1174 1178 strcmdopt = pycompat.strkwargs(cmdoptions)
1175 1179 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1176 1180 try:
1177 1181 return runcommand(
1178 1182 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1179 1183 )
1180 1184 finally:
1181 1185 if repo and repo != req.repo:
1182 1186 repo.close()
1183 1187
1184 1188
1185 1189 def _runcommand(ui, options, cmd, cmdfunc):
1186 1190 """Run a command function, possibly with profiling enabled."""
1187 1191 try:
1188 1192 with tracing.log("Running %s command" % cmd):
1189 1193 return cmdfunc()
1190 1194 except error.SignatureError:
1191 1195 raise error.CommandError(cmd, _(b'invalid arguments'))
1192 1196
1193 1197
1194 1198 def _exceptionwarning(ui):
1195 1199 """Produce a warning message for the current active exception"""
1196 1200
1197 1201 # For compatibility checking, we discard the portion of the hg
1198 1202 # version after the + on the assumption that if a "normal
1199 1203 # user" is running a build with a + in it the packager
1200 1204 # probably built from fairly close to a tag and anyone with a
1201 1205 # 'make local' copy of hg (where the version number can be out
1202 1206 # of date) will be clueful enough to notice the implausible
1203 1207 # version number and try updating.
1204 1208 ct = util.versiontuple(n=2)
1205 1209 worst = None, ct, b''
1206 1210 if ui.config(b'ui', b'supportcontact') is None:
1207 1211 for name, mod in extensions.extensions():
1208 1212 # 'testedwith' should be bytes, but not all extensions are ported
1209 1213 # to py3 and we don't want UnicodeException because of that.
1210 1214 testedwith = stringutil.forcebytestr(
1211 1215 getattr(mod, 'testedwith', b'')
1212 1216 )
1213 1217 report = getattr(mod, 'buglink', _(b'the extension author.'))
1214 1218 if not testedwith.strip():
1215 1219 # We found an untested extension. It's likely the culprit.
1216 1220 worst = name, b'unknown', report
1217 1221 break
1218 1222
1219 1223 # Never blame on extensions bundled with Mercurial.
1220 1224 if extensions.ismoduleinternal(mod):
1221 1225 continue
1222 1226
1223 1227 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1224 1228 if ct in tested:
1225 1229 continue
1226 1230
1227 1231 lower = [t for t in tested if t < ct]
1228 1232 nearest = max(lower or tested)
1229 1233 if worst[0] is None or nearest < worst[1]:
1230 1234 worst = name, nearest, report
1231 1235 if worst[0] is not None:
1232 1236 name, testedwith, report = worst
1233 1237 if not isinstance(testedwith, (bytes, str)):
1234 1238 testedwith = b'.'.join(
1235 1239 [stringutil.forcebytestr(c) for c in testedwith]
1236 1240 )
1237 1241 warning = _(
1238 1242 b'** Unknown exception encountered with '
1239 1243 b'possibly-broken third-party extension %s\n'
1240 1244 b'** which supports versions %s of Mercurial.\n'
1241 1245 b'** Please disable %s and try your action again.\n'
1242 1246 b'** If that fixes the bug please report it to %s\n'
1243 1247 ) % (name, testedwith, name, stringutil.forcebytestr(report))
1244 1248 else:
1245 1249 bugtracker = ui.config(b'ui', b'supportcontact')
1246 1250 if bugtracker is None:
1247 1251 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1248 1252 warning = (
1249 1253 _(
1250 1254 b"** unknown exception encountered, "
1251 1255 b"please report by visiting\n** "
1252 1256 )
1253 1257 + bugtracker
1254 1258 + b'\n'
1255 1259 )
1256 1260 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1257 1261 warning += (
1258 1262 (_(b"** Python %s\n") % sysversion)
1259 1263 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1260 1264 + (
1261 1265 _(b"** Extensions loaded: %s\n")
1262 1266 % b", ".join([x[0] for x in extensions.extensions()])
1263 1267 )
1264 1268 )
1265 1269 return warning
1266 1270
1267 1271
1268 1272 def handlecommandexception(ui):
1269 1273 """Produce a warning message for broken commands
1270 1274
1271 1275 Called when handling an exception; the exception is reraised if
1272 1276 this function returns False, ignored otherwise.
1273 1277 """
1274 1278 warning = _exceptionwarning(ui)
1275 1279 ui.log(
1276 1280 b"commandexception",
1277 1281 b"%s\n%s\n",
1278 1282 warning,
1279 1283 pycompat.sysbytes(traceback.format_exc()),
1280 1284 )
1281 1285 ui.warn(warning)
1282 1286 return False # re-raise the exception
@@ -1,273 +1,283 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 hg: parse 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 no-windows
60 60 $ chmod u-r $TESTTMP/included
61 61 $ hg showconfig section
62 62 hg: parse error at $TESTTMP/hgrc:2: cannot include $TESTTMP/included (Permission denied)
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 hg: parse error at $TESTTMP/hgrc:2: x = y
72 72 unexpected leading whitespace
73 73 [255]
74 74
75 75 $ "$PYTHON" -c "from __future__ import print_function; print('[foo]\nbar = a\n b\n c \n de\n fg \nbaz = bif cb \n')" \
76 76 > > $HGRC
77 77 $ hg showconfig foo
78 78 foo.bar=a\nb\nc\nde\nfg
79 79 foo.baz=bif cb
80 80
81 81 $ FAKEPATH=/path/to/nowhere
82 82 $ export FAKEPATH
83 83 $ echo '%include $FAKEPATH/no-such-file' > $HGRC
84 84 $ hg version
85 85 Mercurial Distributed SCM (version *) (glob)
86 86 (see https://mercurial-scm.org for more information)
87 87
88 88 Copyright (C) 2005-* Matt Mackall and others (glob)
89 89 This is free software; see the source for copying conditions. There is NO
90 90 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
91 91 $ unset FAKEPATH
92 92
93 93 make sure global options given on the cmdline take precedence
94 94
95 95 $ hg showconfig --config ui.verbose=True --quiet
96 96 bundle.mainreporoot=$TESTTMP
97 97 ui.verbose=False
98 98 ui.debug=False
99 99 ui.quiet=True
100 100
101 101 $ touch foobar/untracked
102 102 $ cat >> foobar/.hg/hgrc <<EOF
103 103 > [ui]
104 104 > verbose=True
105 105 > EOF
106 106 $ hg -R foobar st -q
107 107
108 108 username expansion
109 109
110 110 $ olduser=$HGUSER
111 111 $ unset HGUSER
112 112
113 113 $ FAKEUSER='John Doe'
114 114 $ export FAKEUSER
115 115 $ echo '[ui]' > $HGRC
116 116 $ echo 'username = $FAKEUSER' >> $HGRC
117 117
118 118 $ hg init usertest
119 119 $ cd usertest
120 120 $ touch bar
121 121 $ hg commit --addremove --quiet -m "added bar"
122 122 $ hg log --template "{author}\n"
123 123 John Doe
124 124 $ cd ..
125 125
126 126 $ hg showconfig
127 127 bundle.mainreporoot=$TESTTMP
128 128 ui.username=$FAKEUSER
129 129
130 130 $ unset FAKEUSER
131 131 $ HGUSER=$olduser
132 132 $ export HGUSER
133 133
134 134 showconfig with multiple arguments
135 135
136 136 $ echo "[alias]" > $HGRC
137 137 $ echo "log = log -g" >> $HGRC
138 138 $ echo "[defaults]" >> $HGRC
139 139 $ echo "identify = -n" >> $HGRC
140 140 $ hg showconfig alias defaults
141 141 alias.log=log -g
142 142 defaults.identify=-n
143 143 $ hg showconfig alias alias
144 144 alias.log=log -g
145 145 $ hg showconfig alias.log alias.log
146 146 alias.log=log -g
147 147 $ hg showconfig alias defaults.identify
148 148 alias.log=log -g
149 149 defaults.identify=-n
150 150 $ hg showconfig alias.log defaults.identify
151 151 alias.log=log -g
152 152 defaults.identify=-n
153 153
154 154 HGPLAIN
155 155
156 156 $ echo "[ui]" > $HGRC
157 157 $ echo "debug=true" >> $HGRC
158 158 $ echo "fallbackencoding=ASCII" >> $HGRC
159 159 $ echo "quiet=true" >> $HGRC
160 160 $ echo "slash=true" >> $HGRC
161 161 $ echo "traceback=true" >> $HGRC
162 162 $ echo "verbose=true" >> $HGRC
163 163 $ echo "style=~/.hgstyle" >> $HGRC
164 164 $ echo "logtemplate={node}" >> $HGRC
165 165 $ echo "[defaults]" >> $HGRC
166 166 $ echo "identify=-n" >> $HGRC
167 167 $ echo "[alias]" >> $HGRC
168 168 $ echo "log=log -g" >> $HGRC
169 169
170 170 customized hgrc
171 171
172 172 $ hg showconfig
173 173 read config from: $TESTTMP/hgrc
174 174 $TESTTMP/hgrc:13: alias.log=log -g
175 175 repo: bundle.mainreporoot=$TESTTMP
176 176 $TESTTMP/hgrc:11: defaults.identify=-n
177 177 $TESTTMP/hgrc:2: ui.debug=true
178 178 $TESTTMP/hgrc:3: ui.fallbackencoding=ASCII
179 179 $TESTTMP/hgrc:4: ui.quiet=true
180 180 $TESTTMP/hgrc:5: ui.slash=true
181 181 $TESTTMP/hgrc:6: ui.traceback=true
182 182 $TESTTMP/hgrc:7: ui.verbose=true
183 183 $TESTTMP/hgrc:8: ui.style=~/.hgstyle
184 184 $TESTTMP/hgrc:9: ui.logtemplate={node}
185 185
186 186 plain hgrc
187 187
188 188 $ HGPLAIN=; export HGPLAIN
189 189 $ hg showconfig --config ui.traceback=True --debug
190 190 read config from: $TESTTMP/hgrc
191 191 repo: bundle.mainreporoot=$TESTTMP
192 192 --config: ui.traceback=True
193 193 --verbose: ui.verbose=False
194 194 --debug: ui.debug=True
195 195 --quiet: ui.quiet=False
196 196
197 197 with environment variables
198 198
199 199 $ PAGER=p1 EDITOR=e1 VISUAL=e2 hg showconfig --debug
200 200 read config from: $TESTTMP/hgrc
201 201 repo: bundle.mainreporoot=$TESTTMP
202 202 $PAGER: pager.pager=p1
203 203 $VISUAL: ui.editor=e2
204 204 --verbose: ui.verbose=False
205 205 --debug: ui.debug=True
206 206 --quiet: ui.quiet=False
207 207
208 208 plain mode with exceptions
209 209
210 210 $ cat > plain.py <<EOF
211 211 > from mercurial import commands, extensions
212 212 > def _config(orig, ui, repo, *values, **opts):
213 213 > ui.write(b'plain: %r\n' % ui.plain())
214 214 > return orig(ui, repo, *values, **opts)
215 215 > def uisetup(ui):
216 216 > extensions.wrapcommand(commands.table, b'config', _config)
217 217 > EOF
218 218 $ echo "[extensions]" >> $HGRC
219 219 $ echo "plain=./plain.py" >> $HGRC
220 220 $ HGPLAINEXCEPT=; export HGPLAINEXCEPT
221 221 $ hg showconfig --config ui.traceback=True --debug
222 222 plain: True
223 223 read config from: $TESTTMP/hgrc
224 224 repo: bundle.mainreporoot=$TESTTMP
225 225 $TESTTMP/hgrc:15: extensions.plain=./plain.py
226 226 --config: ui.traceback=True
227 227 --verbose: ui.verbose=False
228 228 --debug: ui.debug=True
229 229 --quiet: ui.quiet=False
230 230 $ unset HGPLAIN
231 231 $ hg showconfig --config ui.traceback=True --debug
232 232 plain: True
233 233 read config from: $TESTTMP/hgrc
234 234 repo: bundle.mainreporoot=$TESTTMP
235 235 $TESTTMP/hgrc:15: extensions.plain=./plain.py
236 236 --config: ui.traceback=True
237 237 --verbose: ui.verbose=False
238 238 --debug: ui.debug=True
239 239 --quiet: ui.quiet=False
240 240 $ HGPLAINEXCEPT=i18n; export HGPLAINEXCEPT
241 241 $ hg showconfig --config ui.traceback=True --debug
242 242 plain: True
243 243 read config from: $TESTTMP/hgrc
244 244 repo: bundle.mainreporoot=$TESTTMP
245 245 $TESTTMP/hgrc:15: extensions.plain=./plain.py
246 246 --config: ui.traceback=True
247 247 --verbose: ui.verbose=False
248 248 --debug: ui.debug=True
249 249 --quiet: ui.quiet=False
250 250
251 251 source of paths is not mangled
252 252
253 253 $ cat >> $HGRCPATH <<EOF
254 254 > [paths]
255 255 > foo = bar
256 256 > EOF
257 257 $ hg showconfig --debug paths
258 258 plain: True
259 259 read config from: $TESTTMP/hgrc
260 260 $TESTTMP/hgrc:17: paths.foo=$TESTTMP/bar
261 261
262 262 Test we can skip the user configuration
263 263
264 264 $ cat >> .hg/hgrc <<EOF
265 265 > [paths]
266 266 > elephant = babar
267 267 > EOF
268 268 $ hg path
269 269 elephant = $TESTTMP/babar
270 270 foo = $TESTTMP/bar
271 271 $ HGRCSKIPREPO=1 hg path
272 272 foo = $TESTTMP/bar
273 273
274 $ cat >> .hg/hgrc <<EOF
275 > [broken
276 > EOF
277
278 $ hg path
279 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
280 [255]
281 $ HGRCSKIPREPO=1 hg path
282 foo = $TESTTMP/bar
283
General Comments 0
You need to be logged in to leave comments. Login now