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