##// END OF EJS Templates
registrar: add support for storing the type of command in func object...
Pulkit Goyal -
r34782:fe987d0b default
parent child Browse files
Show More
@@ -1,1021 +1,1024 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 time
19 19 import traceback
20 20
21 21
22 22 from .i18n import _
23 23
24 24 from . import (
25 25 cmdutil,
26 26 color,
27 27 commands,
28 28 demandimport,
29 29 encoding,
30 30 error,
31 31 extensions,
32 32 fancyopts,
33 33 help,
34 34 hg,
35 35 hook,
36 36 profiling,
37 37 pycompat,
38 registrar,
38 39 scmutil,
39 40 ui as uimod,
40 41 util,
41 42 )
42 43
44 unrecoverablewrite = registrar.command.unrecoverablewrite
45
43 46 class request(object):
44 47 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
45 48 ferr=None, prereposetups=None):
46 49 self.args = args
47 50 self.ui = ui
48 51 self.repo = repo
49 52
50 53 # input/output/error streams
51 54 self.fin = fin
52 55 self.fout = fout
53 56 self.ferr = ferr
54 57
55 58 # reposetups which run before extensions, useful for chg to pre-fill
56 59 # low-level repo state (for example, changelog) before extensions.
57 60 self.prereposetups = prereposetups or []
58 61
59 62 def _runexithandlers(self):
60 63 exc = None
61 64 handlers = self.ui._exithandlers
62 65 try:
63 66 while handlers:
64 67 func, args, kwargs = handlers.pop()
65 68 try:
66 69 func(*args, **kwargs)
67 70 except: # re-raises below
68 71 if exc is None:
69 72 exc = sys.exc_info()[1]
70 73 self.ui.warn(('error in exit handlers:\n'))
71 74 self.ui.traceback(force=True)
72 75 finally:
73 76 if exc is not None:
74 77 raise exc
75 78
76 79 def run():
77 80 "run the command in sys.argv"
78 81 _initstdio()
79 82 req = request(pycompat.sysargv[1:])
80 83 err = None
81 84 try:
82 85 status = (dispatch(req) or 0) & 255
83 86 except error.StdioError as e:
84 87 err = e
85 88 status = -1
86 89 if util.safehasattr(req.ui, 'fout'):
87 90 try:
88 91 req.ui.fout.flush()
89 92 except IOError as e:
90 93 err = e
91 94 status = -1
92 95 if util.safehasattr(req.ui, 'ferr'):
93 96 if err is not None and err.errno != errno.EPIPE:
94 97 req.ui.ferr.write('abort: %s\n' %
95 98 encoding.strtolocal(err.strerror))
96 99 req.ui.ferr.flush()
97 100 sys.exit(status & 255)
98 101
99 102 def _initstdio():
100 103 for fp in (sys.stdin, sys.stdout, sys.stderr):
101 104 util.setbinary(fp)
102 105
103 106 def _getsimilar(symbols, value):
104 107 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
105 108 # The cutoff for similarity here is pretty arbitrary. It should
106 109 # probably be investigated and tweaked.
107 110 return [s for s in symbols if sim(s) > 0.6]
108 111
109 112 def _reportsimilar(write, similar):
110 113 if len(similar) == 1:
111 114 write(_("(did you mean %s?)\n") % similar[0])
112 115 elif similar:
113 116 ss = ", ".join(sorted(similar))
114 117 write(_("(did you mean one of %s?)\n") % ss)
115 118
116 119 def _formatparse(write, inst):
117 120 similar = []
118 121 if isinstance(inst, error.UnknownIdentifier):
119 122 # make sure to check fileset first, as revset can invoke fileset
120 123 similar = _getsimilar(inst.symbols, inst.function)
121 124 if len(inst.args) > 1:
122 125 write(_("hg: parse error at %s: %s\n") %
123 126 (inst.args[1], inst.args[0]))
124 127 if (inst.args[0][0] == ' '):
125 128 write(_("unexpected leading whitespace\n"))
126 129 else:
127 130 write(_("hg: parse error: %s\n") % inst.args[0])
128 131 _reportsimilar(write, similar)
129 132 if inst.hint:
130 133 write(_("(%s)\n") % inst.hint)
131 134
132 135 def _formatargs(args):
133 136 return ' '.join(util.shellquote(a) for a in args)
134 137
135 138 def dispatch(req):
136 139 "run the command specified in req.args"
137 140 if req.ferr:
138 141 ferr = req.ferr
139 142 elif req.ui:
140 143 ferr = req.ui.ferr
141 144 else:
142 145 ferr = util.stderr
143 146
144 147 try:
145 148 if not req.ui:
146 149 req.ui = uimod.ui.load()
147 150 if '--traceback' in req.args:
148 151 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
149 152
150 153 # set ui streams from the request
151 154 if req.fin:
152 155 req.ui.fin = req.fin
153 156 if req.fout:
154 157 req.ui.fout = req.fout
155 158 if req.ferr:
156 159 req.ui.ferr = req.ferr
157 160 except error.Abort as inst:
158 161 ferr.write(_("abort: %s\n") % inst)
159 162 if inst.hint:
160 163 ferr.write(_("(%s)\n") % inst.hint)
161 164 return -1
162 165 except error.ParseError as inst:
163 166 _formatparse(ferr.write, inst)
164 167 return -1
165 168
166 169 msg = _formatargs(req.args)
167 170 starttime = util.timer()
168 171 ret = None
169 172 try:
170 173 ret = _runcatch(req)
171 174 except error.ProgrammingError as inst:
172 175 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
173 176 if inst.hint:
174 177 req.ui.warn(_('** (%s)\n') % inst.hint)
175 178 raise
176 179 except KeyboardInterrupt as inst:
177 180 try:
178 181 if isinstance(inst, error.SignalInterrupt):
179 182 msg = _("killed!\n")
180 183 else:
181 184 msg = _("interrupted!\n")
182 185 req.ui.warn(msg)
183 186 except error.SignalInterrupt:
184 187 # maybe pager would quit without consuming all the output, and
185 188 # SIGPIPE was raised. we cannot print anything in this case.
186 189 pass
187 190 except IOError as inst:
188 191 if inst.errno != errno.EPIPE:
189 192 raise
190 193 ret = -1
191 194 finally:
192 195 duration = util.timer() - starttime
193 196 req.ui.flush()
194 197 if req.ui.logblockedtimes:
195 198 req.ui._blockedtimes['command_duration'] = duration * 1000
196 199 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
197 200 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
198 201 msg, ret or 0, duration)
199 202 try:
200 203 req._runexithandlers()
201 204 except: # exiting, so no re-raises
202 205 ret = ret or -1
203 206 return ret
204 207
205 208 def _runcatch(req):
206 209 def catchterm(*args):
207 210 raise error.SignalInterrupt
208 211
209 212 ui = req.ui
210 213 try:
211 214 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
212 215 num = getattr(signal, name, None)
213 216 if num:
214 217 signal.signal(num, catchterm)
215 218 except ValueError:
216 219 pass # happens if called in a thread
217 220
218 221 def _runcatchfunc():
219 222 realcmd = None
220 223 try:
221 224 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
222 225 cmd = cmdargs[0]
223 226 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
224 227 realcmd = aliases[0]
225 228 except (error.UnknownCommand, error.AmbiguousCommand,
226 229 IndexError, getopt.GetoptError):
227 230 # Don't handle this here. We know the command is
228 231 # invalid, but all we're worried about for now is that
229 232 # it's not a command that server operators expect to
230 233 # be safe to offer to users in a sandbox.
231 234 pass
232 235 if realcmd == 'serve' and '--stdio' in cmdargs:
233 236 # We want to constrain 'hg serve --stdio' instances pretty
234 237 # closely, as many shared-ssh access tools want to grant
235 238 # access to run *only* 'hg -R $repo serve --stdio'. We
236 239 # restrict to exactly that set of arguments, and prohibit
237 240 # any repo name that starts with '--' to prevent
238 241 # shenanigans wherein a user does something like pass
239 242 # --debugger or --config=ui.debugger=1 as a repo
240 243 # name. This used to actually run the debugger.
241 244 if (len(req.args) != 4 or
242 245 req.args[0] != '-R' or
243 246 req.args[1].startswith('--') or
244 247 req.args[2] != 'serve' or
245 248 req.args[3] != '--stdio'):
246 249 raise error.Abort(
247 250 _('potentially unsafe serve --stdio invocation: %r') %
248 251 (req.args,))
249 252
250 253 try:
251 254 debugger = 'pdb'
252 255 debugtrace = {
253 256 'pdb': pdb.set_trace
254 257 }
255 258 debugmortem = {
256 259 'pdb': pdb.post_mortem
257 260 }
258 261
259 262 # read --config before doing anything else
260 263 # (e.g. to change trust settings for reading .hg/hgrc)
261 264 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
262 265
263 266 if req.repo:
264 267 # copy configs that were passed on the cmdline (--config) to
265 268 # the repo ui
266 269 for sec, name, val in cfgs:
267 270 req.repo.ui.setconfig(sec, name, val, source='--config')
268 271
269 272 # developer config: ui.debugger
270 273 debugger = ui.config("ui", "debugger")
271 274 debugmod = pdb
272 275 if not debugger or ui.plain():
273 276 # if we are in HGPLAIN mode, then disable custom debugging
274 277 debugger = 'pdb'
275 278 elif '--debugger' in req.args:
276 279 # This import can be slow for fancy debuggers, so only
277 280 # do it when absolutely necessary, i.e. when actual
278 281 # debugging has been requested
279 282 with demandimport.deactivated():
280 283 try:
281 284 debugmod = __import__(debugger)
282 285 except ImportError:
283 286 pass # Leave debugmod = pdb
284 287
285 288 debugtrace[debugger] = debugmod.set_trace
286 289 debugmortem[debugger] = debugmod.post_mortem
287 290
288 291 # enter the debugger before command execution
289 292 if '--debugger' in req.args:
290 293 ui.warn(_("entering debugger - "
291 294 "type c to continue starting hg or h for help\n"))
292 295
293 296 if (debugger != 'pdb' and
294 297 debugtrace[debugger] == debugtrace['pdb']):
295 298 ui.warn(_("%s debugger specified "
296 299 "but its module was not found\n") % debugger)
297 300 with demandimport.deactivated():
298 301 debugtrace[debugger]()
299 302 try:
300 303 return _dispatch(req)
301 304 finally:
302 305 ui.flush()
303 306 except: # re-raises
304 307 # enter the debugger when we hit an exception
305 308 if '--debugger' in req.args:
306 309 traceback.print_exc()
307 310 debugmortem[debugger](sys.exc_info()[2])
308 311 raise
309 312
310 313 return _callcatch(ui, _runcatchfunc)
311 314
312 315 def _callcatch(ui, func):
313 316 """like scmutil.callcatch but handles more high-level exceptions about
314 317 config parsing and commands. besides, use handlecommandexception to handle
315 318 uncaught exceptions.
316 319 """
317 320 try:
318 321 return scmutil.callcatch(ui, func)
319 322 except error.AmbiguousCommand as inst:
320 323 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
321 324 (inst.args[0], " ".join(inst.args[1])))
322 325 except error.CommandError as inst:
323 326 if inst.args[0]:
324 327 ui.pager('help')
325 328 msgbytes = pycompat.bytestr(inst.args[1])
326 329 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
327 330 commands.help_(ui, inst.args[0], full=False, command=True)
328 331 else:
329 332 ui.pager('help')
330 333 ui.warn(_("hg: %s\n") % inst.args[1])
331 334 commands.help_(ui, 'shortlist')
332 335 except error.ParseError as inst:
333 336 _formatparse(ui.warn, inst)
334 337 return -1
335 338 except error.UnknownCommand as inst:
336 339 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
337 340 try:
338 341 # check if the command is in a disabled extension
339 342 # (but don't check for extensions themselves)
340 343 formatted = help.formattedhelp(ui, commands, inst.args[0],
341 344 unknowncmd=True)
342 345 ui.warn(nocmdmsg)
343 346 ui.write(formatted)
344 347 except (error.UnknownCommand, error.Abort):
345 348 suggested = False
346 349 if len(inst.args) == 2:
347 350 sim = _getsimilar(inst.args[1], inst.args[0])
348 351 if sim:
349 352 ui.warn(nocmdmsg)
350 353 _reportsimilar(ui.warn, sim)
351 354 suggested = True
352 355 if not suggested:
353 356 ui.pager('help')
354 357 ui.warn(nocmdmsg)
355 358 commands.help_(ui, 'shortlist')
356 359 except IOError:
357 360 raise
358 361 except KeyboardInterrupt:
359 362 raise
360 363 except: # probably re-raises
361 364 if not handlecommandexception(ui):
362 365 raise
363 366
364 367 return -1
365 368
366 369 def aliasargs(fn, givenargs):
367 370 args = []
368 371 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
369 372 if not util.safehasattr(fn, '_origfunc'):
370 373 args = getattr(fn, 'args', args)
371 374 if args:
372 375 cmd = ' '.join(map(util.shellquote, args))
373 376
374 377 nums = []
375 378 def replacer(m):
376 379 num = int(m.group(1)) - 1
377 380 nums.append(num)
378 381 if num < len(givenargs):
379 382 return givenargs[num]
380 383 raise error.Abort(_('too few arguments for command alias'))
381 384 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
382 385 givenargs = [x for i, x in enumerate(givenargs)
383 386 if i not in nums]
384 387 args = pycompat.shlexsplit(cmd)
385 388 return args + givenargs
386 389
387 390 def aliasinterpolate(name, args, cmd):
388 391 '''interpolate args into cmd for shell aliases
389 392
390 393 This also handles $0, $@ and "$@".
391 394 '''
392 395 # util.interpolate can't deal with "$@" (with quotes) because it's only
393 396 # built to match prefix + patterns.
394 397 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
395 398 replacemap['$0'] = name
396 399 replacemap['$$'] = '$'
397 400 replacemap['$@'] = ' '.join(args)
398 401 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
399 402 # parameters, separated out into words. Emulate the same behavior here by
400 403 # quoting the arguments individually. POSIX shells will then typically
401 404 # tokenize each argument into exactly one word.
402 405 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
403 406 # escape '\$' for regex
404 407 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
405 408 r = re.compile(regex)
406 409 return r.sub(lambda x: replacemap[x.group()], cmd)
407 410
408 411 class cmdalias(object):
409 412 def __init__(self, name, definition, cmdtable, source):
410 413 self.name = self.cmd = name
411 414 self.cmdname = ''
412 415 self.definition = definition
413 416 self.fn = None
414 417 self.givenargs = []
415 418 self.opts = []
416 419 self.help = ''
417 420 self.badalias = None
418 421 self.unknowncmd = False
419 422 self.source = source
420 423
421 424 try:
422 425 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
423 426 for alias, e in cmdtable.iteritems():
424 427 if e is entry:
425 428 self.cmd = alias
426 429 break
427 430 self.shadows = True
428 431 except error.UnknownCommand:
429 432 self.shadows = False
430 433
431 434 if not self.definition:
432 435 self.badalias = _("no definition for alias '%s'") % self.name
433 436 return
434 437
435 438 if self.definition.startswith('!'):
436 439 self.shell = True
437 440 def fn(ui, *args):
438 441 env = {'HG_ARGS': ' '.join((self.name,) + args)}
439 442 def _checkvar(m):
440 443 if m.groups()[0] == '$':
441 444 return m.group()
442 445 elif int(m.groups()[0]) <= len(args):
443 446 return m.group()
444 447 else:
445 448 ui.debug("No argument found for substitution "
446 449 "of %i variable in alias '%s' definition."
447 450 % (int(m.groups()[0]), self.name))
448 451 return ''
449 452 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
450 453 cmd = aliasinterpolate(self.name, args, cmd)
451 454 return ui.system(cmd, environ=env,
452 455 blockedtag='alias_%s' % self.name)
453 456 self.fn = fn
454 457 return
455 458
456 459 try:
457 460 args = pycompat.shlexsplit(self.definition)
458 461 except ValueError as inst:
459 462 self.badalias = (_("error in definition for alias '%s': %s")
460 463 % (self.name, inst))
461 464 return
462 465 self.cmdname = cmd = args.pop(0)
463 466 self.givenargs = args
464 467
465 468 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
466 469 if _earlygetopt([invalidarg], args):
467 470 self.badalias = (_("error in definition for alias '%s': %s may "
468 471 "only be given on the command line")
469 472 % (self.name, invalidarg))
470 473 return
471 474
472 475 try:
473 476 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
474 477 if len(tableentry) > 2:
475 478 self.fn, self.opts, self.help = tableentry
476 479 else:
477 480 self.fn, self.opts = tableentry
478 481
479 482 if self.help.startswith("hg " + cmd):
480 483 # drop prefix in old-style help lines so hg shows the alias
481 484 self.help = self.help[4 + len(cmd):]
482 485 self.__doc__ = self.fn.__doc__
483 486
484 487 except error.UnknownCommand:
485 488 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
486 489 % (self.name, cmd))
487 490 self.unknowncmd = True
488 491 except error.AmbiguousCommand:
489 492 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
490 493 % (self.name, cmd))
491 494
492 495 @property
493 496 def args(self):
494 497 args = pycompat.maplist(util.expandpath, self.givenargs)
495 498 return aliasargs(self.fn, args)
496 499
497 500 def __getattr__(self, name):
498 adefaults = {r'norepo': True,
501 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
499 502 r'optionalrepo': False, r'inferrepo': False}
500 503 if name not in adefaults:
501 504 raise AttributeError(name)
502 505 if self.badalias or util.safehasattr(self, 'shell'):
503 506 return adefaults[name]
504 507 return getattr(self.fn, name)
505 508
506 509 def __call__(self, ui, *args, **opts):
507 510 if self.badalias:
508 511 hint = None
509 512 if self.unknowncmd:
510 513 try:
511 514 # check if the command is in a disabled extension
512 515 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
513 516 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
514 517 except error.UnknownCommand:
515 518 pass
516 519 raise error.Abort(self.badalias, hint=hint)
517 520 if self.shadows:
518 521 ui.debug("alias '%s' shadows command '%s'\n" %
519 522 (self.name, self.cmdname))
520 523
521 524 ui.log('commandalias', "alias '%s' expands to '%s'\n",
522 525 self.name, self.definition)
523 526 if util.safehasattr(self, 'shell'):
524 527 return self.fn(ui, *args, **opts)
525 528 else:
526 529 try:
527 530 return util.checksignature(self.fn)(ui, *args, **opts)
528 531 except error.SignatureError:
529 532 args = ' '.join([self.cmdname] + self.args)
530 533 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
531 534 raise
532 535
533 536 class lazyaliasentry(object):
534 537 """like a typical command entry (func, opts, help), but is lazy"""
535 538
536 539 def __init__(self, name, definition, cmdtable, source):
537 540 self.name = name
538 541 self.definition = definition
539 542 self.cmdtable = cmdtable.copy()
540 543 self.source = source
541 544
542 545 @util.propertycache
543 546 def _aliasdef(self):
544 547 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
545 548
546 549 def __getitem__(self, n):
547 550 aliasdef = self._aliasdef
548 551 if n == 0:
549 552 return aliasdef
550 553 elif n == 1:
551 554 return aliasdef.opts
552 555 elif n == 2:
553 556 return aliasdef.help
554 557 else:
555 558 raise IndexError
556 559
557 560 def __iter__(self):
558 561 for i in range(3):
559 562 yield self[i]
560 563
561 564 def __len__(self):
562 565 return 3
563 566
564 567 def addaliases(ui, cmdtable):
565 568 # aliases are processed after extensions have been loaded, so they
566 569 # may use extension commands. Aliases can also use other alias definitions,
567 570 # but only if they have been defined prior to the current definition.
568 571 for alias, definition in ui.configitems('alias'):
569 572 try:
570 573 if cmdtable[alias].definition == definition:
571 574 continue
572 575 except (KeyError, AttributeError):
573 576 # definition might not exist or it might not be a cmdalias
574 577 pass
575 578
576 579 source = ui.configsource('alias', alias)
577 580 entry = lazyaliasentry(alias, definition, cmdtable, source)
578 581 cmdtable[alias] = entry
579 582
580 583 def _parse(ui, args):
581 584 options = {}
582 585 cmdoptions = {}
583 586
584 587 try:
585 588 args = fancyopts.fancyopts(args, commands.globalopts, options)
586 589 except getopt.GetoptError as inst:
587 590 raise error.CommandError(None, inst)
588 591
589 592 if args:
590 593 cmd, args = args[0], args[1:]
591 594 aliases, entry = cmdutil.findcmd(cmd, commands.table,
592 595 ui.configbool("ui", "strict"))
593 596 cmd = aliases[0]
594 597 args = aliasargs(entry[0], args)
595 598 defaults = ui.config("defaults", cmd)
596 599 if defaults:
597 600 args = pycompat.maplist(
598 601 util.expandpath, pycompat.shlexsplit(defaults)) + args
599 602 c = list(entry[1])
600 603 else:
601 604 cmd = None
602 605 c = []
603 606
604 607 # combine global options into local
605 608 for o in commands.globalopts:
606 609 c.append((o[0], o[1], options[o[1]], o[3]))
607 610
608 611 try:
609 612 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
610 613 except getopt.GetoptError as inst:
611 614 raise error.CommandError(cmd, inst)
612 615
613 616 # separate global options back out
614 617 for o in commands.globalopts:
615 618 n = o[1]
616 619 options[n] = cmdoptions[n]
617 620 del cmdoptions[n]
618 621
619 622 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
620 623
621 624 def _parseconfig(ui, config):
622 625 """parse the --config options from the command line"""
623 626 configs = []
624 627
625 628 for cfg in config:
626 629 try:
627 630 name, value = [cfgelem.strip()
628 631 for cfgelem in cfg.split('=', 1)]
629 632 section, name = name.split('.', 1)
630 633 if not section or not name:
631 634 raise IndexError
632 635 ui.setconfig(section, name, value, '--config')
633 636 configs.append((section, name, value))
634 637 except (IndexError, ValueError):
635 638 raise error.Abort(_('malformed --config option: %r '
636 639 '(use --config section.name=value)') % cfg)
637 640
638 641 return configs
639 642
640 643 def _earlygetopt(aliases, args):
641 644 """Return list of values for an option (or aliases).
642 645
643 646 The values are listed in the order they appear in args.
644 647 The options and values are removed from args.
645 648
646 649 >>> args = [b'x', b'--cwd', b'foo', b'y']
647 650 >>> _earlygetopt([b'--cwd'], args), args
648 651 (['foo'], ['x', 'y'])
649 652
650 653 >>> args = [b'x', b'--cwd=bar', b'y']
651 654 >>> _earlygetopt([b'--cwd'], args), args
652 655 (['bar'], ['x', 'y'])
653 656
654 657 >>> args = [b'x', b'-R', b'foo', b'y']
655 658 >>> _earlygetopt([b'-R'], args), args
656 659 (['foo'], ['x', 'y'])
657 660
658 661 >>> args = [b'x', b'-Rbar', b'y']
659 662 >>> _earlygetopt([b'-R'], args), args
660 663 (['bar'], ['x', 'y'])
661 664 """
662 665 try:
663 666 argcount = args.index("--")
664 667 except ValueError:
665 668 argcount = len(args)
666 669 shortopts = [opt for opt in aliases if len(opt) == 2]
667 670 values = []
668 671 pos = 0
669 672 while pos < argcount:
670 673 fullarg = arg = args[pos]
671 674 equals = arg.find('=')
672 675 if equals > -1:
673 676 arg = arg[:equals]
674 677 if arg in aliases:
675 678 del args[pos]
676 679 if equals > -1:
677 680 values.append(fullarg[equals + 1:])
678 681 argcount -= 1
679 682 else:
680 683 if pos + 1 >= argcount:
681 684 # ignore and let getopt report an error if there is no value
682 685 break
683 686 values.append(args.pop(pos))
684 687 argcount -= 2
685 688 elif arg[:2] in shortopts:
686 689 # short option can have no following space, e.g. hg log -Rfoo
687 690 values.append(args.pop(pos)[2:])
688 691 argcount -= 1
689 692 else:
690 693 pos += 1
691 694 return values
692 695
693 696 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
694 697 # run pre-hook, and abort if it fails
695 698 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
696 699 pats=cmdpats, opts=cmdoptions)
697 700 try:
698 701 ret = _runcommand(ui, options, cmd, d)
699 702 # run post-hook, passing command result
700 703 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
701 704 result=ret, pats=cmdpats, opts=cmdoptions)
702 705 except Exception:
703 706 # run failure hook and re-raise
704 707 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
705 708 pats=cmdpats, opts=cmdoptions)
706 709 raise
707 710 return ret
708 711
709 712 def _getlocal(ui, rpath, wd=None):
710 713 """Return (path, local ui object) for the given target path.
711 714
712 715 Takes paths in [cwd]/.hg/hgrc into account."
713 716 """
714 717 if wd is None:
715 718 try:
716 719 wd = pycompat.getcwd()
717 720 except OSError as e:
718 721 raise error.Abort(_("error getting current working directory: %s") %
719 722 encoding.strtolocal(e.strerror))
720 723 path = cmdutil.findrepo(wd) or ""
721 724 if not path:
722 725 lui = ui
723 726 else:
724 727 lui = ui.copy()
725 728 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
726 729
727 730 if rpath and rpath[-1]:
728 731 path = lui.expandpath(rpath[-1])
729 732 lui = ui.copy()
730 733 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
731 734
732 735 return path, lui
733 736
734 737 def _checkshellalias(lui, ui, args):
735 738 """Return the function to run the shell alias, if it is required"""
736 739 options = {}
737 740
738 741 try:
739 742 args = fancyopts.fancyopts(args, commands.globalopts, options)
740 743 except getopt.GetoptError:
741 744 return
742 745
743 746 if not args:
744 747 return
745 748
746 749 cmdtable = commands.table
747 750
748 751 cmd = args[0]
749 752 try:
750 753 strict = ui.configbool("ui", "strict")
751 754 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
752 755 except (error.AmbiguousCommand, error.UnknownCommand):
753 756 return
754 757
755 758 cmd = aliases[0]
756 759 fn = entry[0]
757 760
758 761 if cmd and util.safehasattr(fn, 'shell'):
759 762 d = lambda: fn(ui, *args[1:])
760 763 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
761 764 [], {})
762 765
763 766 def _dispatch(req):
764 767 args = req.args
765 768 ui = req.ui
766 769
767 770 # check for cwd
768 771 cwd = _earlygetopt(['--cwd'], args)
769 772 if cwd:
770 773 os.chdir(cwd[-1])
771 774
772 775 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
773 776 path, lui = _getlocal(ui, rpath)
774 777
775 778 uis = {ui, lui}
776 779
777 780 if req.repo:
778 781 uis.add(req.repo.ui)
779 782
780 783 if '--profile' in args:
781 784 for ui_ in uis:
782 785 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
783 786
784 787 profile = lui.configbool('profiling', 'enabled')
785 788 with profiling.profile(lui, enabled=profile) as profiler:
786 789 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
787 790 # reposetup
788 791 extensions.loadall(lui)
789 792 # Propagate any changes to lui.__class__ by extensions
790 793 ui.__class__ = lui.__class__
791 794
792 795 # (uisetup and extsetup are handled in extensions.loadall)
793 796
794 797 # (reposetup is handled in hg.repository)
795 798
796 799 addaliases(lui, commands.table)
797 800
798 801 # All aliases and commands are completely defined, now.
799 802 # Check abbreviation/ambiguity of shell alias.
800 803 shellaliasfn = _checkshellalias(lui, ui, args)
801 804 if shellaliasfn:
802 805 return shellaliasfn()
803 806
804 807 # check for fallback encoding
805 808 fallback = lui.config('ui', 'fallbackencoding')
806 809 if fallback:
807 810 encoding.fallbackencoding = fallback
808 811
809 812 fullargs = args
810 813 cmd, func, args, options, cmdoptions = _parse(lui, args)
811 814
812 815 if options["config"]:
813 816 raise error.Abort(_("option --config may not be abbreviated!"))
814 817 if options["cwd"]:
815 818 raise error.Abort(_("option --cwd may not be abbreviated!"))
816 819 if options["repository"]:
817 820 raise error.Abort(_(
818 821 "option -R has to be separated from other options (e.g. not "
819 822 "-qR) and --repository may only be abbreviated as --repo!"))
820 823
821 824 if options["encoding"]:
822 825 encoding.encoding = options["encoding"]
823 826 if options["encodingmode"]:
824 827 encoding.encodingmode = options["encodingmode"]
825 828 if options["time"]:
826 829 def get_times():
827 830 t = os.times()
828 831 if t[4] == 0.0:
829 832 # Windows leaves this as zero, so use time.clock()
830 833 t = (t[0], t[1], t[2], t[3], time.clock())
831 834 return t
832 835 s = get_times()
833 836 def print_time():
834 837 t = get_times()
835 838 ui.warn(
836 839 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
837 840 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
838 841 ui.atexit(print_time)
839 842 if options["profile"]:
840 843 profiler.start()
841 844
842 845 if options['verbose'] or options['debug'] or options['quiet']:
843 846 for opt in ('verbose', 'debug', 'quiet'):
844 847 val = str(bool(options[opt]))
845 848 if pycompat.ispy3:
846 849 val = val.encode('ascii')
847 850 for ui_ in uis:
848 851 ui_.setconfig('ui', opt, val, '--' + opt)
849 852
850 853 if options['traceback']:
851 854 for ui_ in uis:
852 855 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
853 856
854 857 if options['noninteractive']:
855 858 for ui_ in uis:
856 859 ui_.setconfig('ui', 'interactive', 'off', '-y')
857 860
858 861 if cmdoptions.get('insecure', False):
859 862 for ui_ in uis:
860 863 ui_.insecureconnections = True
861 864
862 865 # setup color handling before pager, because setting up pager
863 866 # might cause incorrect console information
864 867 coloropt = options['color']
865 868 for ui_ in uis:
866 869 if coloropt:
867 870 ui_.setconfig('ui', 'color', coloropt, '--color')
868 871 color.setup(ui_)
869 872
870 873 if util.parsebool(options['pager']):
871 874 # ui.pager() expects 'internal-always-' prefix in this case
872 875 ui.pager('internal-always-' + cmd)
873 876 elif options['pager'] != 'auto':
874 877 for ui_ in uis:
875 878 ui_.disablepager()
876 879
877 880 if options['version']:
878 881 return commands.version_(ui)
879 882 if options['help']:
880 883 return commands.help_(ui, cmd, command=cmd is not None)
881 884 elif not cmd:
882 885 return commands.help_(ui, 'shortlist')
883 886
884 887 repo = None
885 888 cmdpats = args[:]
886 889 if not func.norepo:
887 890 # use the repo from the request only if we don't have -R
888 891 if not rpath and not cwd:
889 892 repo = req.repo
890 893
891 894 if repo:
892 895 # set the descriptors of the repo ui to those of ui
893 896 repo.ui.fin = ui.fin
894 897 repo.ui.fout = ui.fout
895 898 repo.ui.ferr = ui.ferr
896 899 else:
897 900 try:
898 901 repo = hg.repository(ui, path=path,
899 902 presetupfuncs=req.prereposetups)
900 903 if not repo.local():
901 904 raise error.Abort(_("repository '%s' is not local")
902 905 % path)
903 906 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
904 907 'repo')
905 908 except error.RequirementError:
906 909 raise
907 910 except error.RepoError:
908 911 if rpath and rpath[-1]: # invalid -R path
909 912 raise
910 913 if not func.optionalrepo:
911 914 if func.inferrepo and args and not path:
912 915 # try to infer -R from command args
913 916 repos = map(cmdutil.findrepo, args)
914 917 guess = repos[0]
915 918 if guess and repos.count(guess) == len(repos):
916 919 req.args = ['--repository', guess] + fullargs
917 920 return _dispatch(req)
918 921 if not path:
919 922 raise error.RepoError(_("no repository found in"
920 923 " '%s' (.hg not found)")
921 924 % pycompat.getcwd())
922 925 raise
923 926 if repo:
924 927 ui = repo.ui
925 928 if options['hidden']:
926 929 repo = repo.unfiltered()
927 930 args.insert(0, repo)
928 931 elif rpath:
929 932 ui.warn(_("warning: --repository ignored\n"))
930 933
931 934 msg = _formatargs(fullargs)
932 935 ui.log("command", '%s\n', msg)
933 936 strcmdopt = pycompat.strkwargs(cmdoptions)
934 937 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
935 938 try:
936 939 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
937 940 cmdpats, cmdoptions)
938 941 finally:
939 942 if repo and repo != req.repo:
940 943 repo.close()
941 944
942 945 def _runcommand(ui, options, cmd, cmdfunc):
943 946 """Run a command function, possibly with profiling enabled."""
944 947 try:
945 948 return cmdfunc()
946 949 except error.SignatureError:
947 950 raise error.CommandError(cmd, _('invalid arguments'))
948 951
949 952 def _exceptionwarning(ui):
950 953 """Produce a warning message for the current active exception"""
951 954
952 955 # For compatibility checking, we discard the portion of the hg
953 956 # version after the + on the assumption that if a "normal
954 957 # user" is running a build with a + in it the packager
955 958 # probably built from fairly close to a tag and anyone with a
956 959 # 'make local' copy of hg (where the version number can be out
957 960 # of date) will be clueful enough to notice the implausible
958 961 # version number and try updating.
959 962 ct = util.versiontuple(n=2)
960 963 worst = None, ct, ''
961 964 if ui.config('ui', 'supportcontact') is None:
962 965 for name, mod in extensions.extensions():
963 966 testedwith = getattr(mod, 'testedwith', '')
964 967 if pycompat.ispy3 and isinstance(testedwith, str):
965 968 testedwith = testedwith.encode(u'utf-8')
966 969 report = getattr(mod, 'buglink', _('the extension author.'))
967 970 if not testedwith.strip():
968 971 # We found an untested extension. It's likely the culprit.
969 972 worst = name, 'unknown', report
970 973 break
971 974
972 975 # Never blame on extensions bundled with Mercurial.
973 976 if extensions.ismoduleinternal(mod):
974 977 continue
975 978
976 979 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
977 980 if ct in tested:
978 981 continue
979 982
980 983 lower = [t for t in tested if t < ct]
981 984 nearest = max(lower or tested)
982 985 if worst[0] is None or nearest < worst[1]:
983 986 worst = name, nearest, report
984 987 if worst[0] is not None:
985 988 name, testedwith, report = worst
986 989 if not isinstance(testedwith, (bytes, str)):
987 990 testedwith = '.'.join([str(c) for c in testedwith])
988 991 warning = (_('** Unknown exception encountered with '
989 992 'possibly-broken third-party extension %s\n'
990 993 '** which supports versions %s of Mercurial.\n'
991 994 '** Please disable %s and try your action again.\n'
992 995 '** If that fixes the bug please report it to %s\n')
993 996 % (name, testedwith, name, report))
994 997 else:
995 998 bugtracker = ui.config('ui', 'supportcontact')
996 999 if bugtracker is None:
997 1000 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
998 1001 warning = (_("** unknown exception encountered, "
999 1002 "please report by visiting\n** ") + bugtracker + '\n')
1000 1003 if pycompat.ispy3:
1001 1004 sysversion = sys.version.encode(u'utf-8')
1002 1005 else:
1003 1006 sysversion = sys.version
1004 1007 sysversion = sysversion.replace('\n', '')
1005 1008 warning += ((_("** Python %s\n") % sysversion) +
1006 1009 (_("** Mercurial Distributed SCM (version %s)\n") %
1007 1010 util.version()) +
1008 1011 (_("** Extensions loaded: %s\n") %
1009 1012 ", ".join([x[0] for x in extensions.extensions()])))
1010 1013 return warning
1011 1014
1012 1015 def handlecommandexception(ui):
1013 1016 """Produce a warning message for broken commands
1014 1017
1015 1018 Called when handling an exception; the exception is reraised if
1016 1019 this function returns False, ignored otherwise.
1017 1020 """
1018 1021 warning = _exceptionwarning(ui)
1019 1022 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1020 1023 ui.warn(warning)
1021 1024 return False # re-raise the exception
@@ -1,381 +1,404 b''
1 1 # registrar.py - utilities to register function for specific purpose
2 2 #
3 3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 from .i18n import _
10 11 from . import (
11 12 configitems,
12 13 error,
13 14 pycompat,
14 15 util,
15 16 )
16 17
17 18 # unlike the other registered items, config options are neither functions or
18 19 # classes. Registering the option is just small function call.
19 20 #
20 21 # We still add the official API to the registrar module for consistency with
21 22 # the other items extensions want might to register.
22 23 configitem = configitems.getitemregister
23 24
24 25 class _funcregistrarbase(object):
25 26 """Base of decorator to register a function for specific purpose
26 27
27 28 This decorator stores decorated functions into own dict 'table'.
28 29
29 30 The least derived class can be defined by overriding 'formatdoc',
30 31 for example::
31 32
32 33 class keyword(_funcregistrarbase):
33 34 _docformat = ":%s: %s"
34 35
35 36 This should be used as below:
36 37
37 38 keyword = registrar.keyword()
38 39
39 40 @keyword('bar')
40 41 def barfunc(*args, **kwargs):
41 42 '''Explanation of bar keyword ....
42 43 '''
43 44 pass
44 45
45 46 In this case:
46 47
47 48 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
48 49 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
49 50 """
50 51 def __init__(self, table=None):
51 52 if table is None:
52 53 self._table = {}
53 54 else:
54 55 self._table = table
55 56
56 57 def __call__(self, decl, *args, **kwargs):
57 58 return lambda func: self._doregister(func, decl, *args, **kwargs)
58 59
59 60 def _doregister(self, func, decl, *args, **kwargs):
60 61 name = self._getname(decl)
61 62
62 63 if name in self._table:
63 64 msg = 'duplicate registration for name: "%s"' % name
64 65 raise error.ProgrammingError(msg)
65 66
66 67 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
67 68 doc = pycompat.sysbytes(func.__doc__).strip()
68 69 func._origdoc = doc
69 70 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
70 71
71 72 self._table[name] = func
72 73 self._extrasetup(name, func, *args, **kwargs)
73 74
74 75 return func
75 76
76 77 def _parsefuncdecl(self, decl):
77 78 """Parse function declaration and return the name of function in it
78 79 """
79 80 i = decl.find('(')
80 81 if i >= 0:
81 82 return decl[:i]
82 83 else:
83 84 return decl
84 85
85 86 def _getname(self, decl):
86 87 """Return the name of the registered function from decl
87 88
88 89 Derived class should override this, if it allows more
89 90 descriptive 'decl' string than just a name.
90 91 """
91 92 return decl
92 93
93 94 _docformat = None
94 95
95 96 def _formatdoc(self, decl, doc):
96 97 """Return formatted document of the registered function for help
97 98
98 99 'doc' is '__doc__.strip()' of the registered function.
99 100 """
100 101 return self._docformat % (decl, doc)
101 102
102 103 def _extrasetup(self, name, func):
103 104 """Execute exra setup for registered function, if needed
104 105 """
105 106
106 107 class command(_funcregistrarbase):
107 108 """Decorator to register a command function to table
108 109
109 110 This class receives a command table as its argument. The table should
110 111 be a dict.
111 112
112 113 The created object can be used as a decorator for adding commands to
113 114 that command table. This accepts multiple arguments to define a command.
114 115
115 116 The first argument is the command name.
116 117
117 118 The options argument is an iterable of tuples defining command arguments.
118 119 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
119 120
120 121 The synopsis argument defines a short, one line summary of how to use the
121 122 command. This shows up in the help output.
122 123
123 124 The norepo argument defines whether the command does not require a
124 125 local repository. Most commands operate against a repository, thus the
125 126 default is False.
126 127
127 128 The optionalrepo argument defines whether the command optionally requires
128 129 a local repository.
129 130
130 131 The inferrepo argument defines whether to try to find a repository from the
131 132 command line arguments. If True, arguments will be examined for potential
132 133 repository locations. See ``findrepo()``. If a repository is found, it
133 134 will be used.
135
136 There are three constants in the class which tells what type of the command
137 that is. That information will be helpful at various places. It will be also
138 be used to decide what level of access the command has on hidden commits.
139 The constants are:
140
141 unrecoverablewrite is for those write commands which can't be recovered like
142 push.
143 recoverablewrite is for write commands which can be recovered like commit.
144 readonly is for commands which are read only.
134 145 """
135 146
147 unrecoverablewrite = "unrecoverable"
148 recoverablewrite = "recoverable"
149 readonly = "readonly"
150
136 151 def _doregister(self, func, name, options=(), synopsis=None,
137 norepo=False, optionalrepo=False, inferrepo=False):
152 norepo=False, optionalrepo=False, inferrepo=False,
153 cmdtype=unrecoverablewrite):
154
155 possiblecmdtypes = set([self.unrecoverablewrite, self.recoverablewrite,
156 self.readonly])
157 if cmdtype not in possiblecmdtypes:
158 raise error.ProgrammingError(_("unknown cmdtype value '%s' for "
159 "'%s' command") % (cmdtype, name))
138 160 func.norepo = norepo
139 161 func.optionalrepo = optionalrepo
140 162 func.inferrepo = inferrepo
163 func.cmdtype = cmdtype
141 164 if synopsis:
142 165 self._table[name] = func, list(options), synopsis
143 166 else:
144 167 self._table[name] = func, list(options)
145 168 return func
146 169
147 170 class revsetpredicate(_funcregistrarbase):
148 171 """Decorator to register revset predicate
149 172
150 173 Usage::
151 174
152 175 revsetpredicate = registrar.revsetpredicate()
153 176
154 177 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
155 178 def mypredicatefunc(repo, subset, x):
156 179 '''Explanation of this revset predicate ....
157 180 '''
158 181 pass
159 182
160 183 The first string argument is used also in online help.
161 184
162 185 Optional argument 'safe' indicates whether a predicate is safe for
163 186 DoS attack (False by default).
164 187
165 188 Optional argument 'takeorder' indicates whether a predicate function
166 189 takes ordering policy as the last argument.
167 190
168 191 Optional argument 'weight' indicates the estimated run-time cost, useful
169 192 for static optimization, default is 1. Higher weight means more expensive.
170 193 Usually, revsets that are fast and return only one revision has a weight of
171 194 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
172 195 changelog have weight 10 (ex. author); revsets reading manifest deltas have
173 196 weight 30 (ex. adds); revset reading manifest contents have weight 100
174 197 (ex. contains). Note: those values are flexible. If the revset has a
175 198 same big-O time complexity as 'contains', but with a smaller constant, it
176 199 might have a weight of 90.
177 200
178 201 'revsetpredicate' instance in example above can be used to
179 202 decorate multiple functions.
180 203
181 204 Decorated functions are registered automatically at loading
182 205 extension, if an instance named as 'revsetpredicate' is used for
183 206 decorating in extension.
184 207
185 208 Otherwise, explicit 'revset.loadpredicate()' is needed.
186 209 """
187 210 _getname = _funcregistrarbase._parsefuncdecl
188 211 _docformat = "``%s``\n %s"
189 212
190 213 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
191 214 func._safe = safe
192 215 func._takeorder = takeorder
193 216 func._weight = weight
194 217
195 218 class filesetpredicate(_funcregistrarbase):
196 219 """Decorator to register fileset predicate
197 220
198 221 Usage::
199 222
200 223 filesetpredicate = registrar.filesetpredicate()
201 224
202 225 @filesetpredicate('mypredicate()')
203 226 def mypredicatefunc(mctx, x):
204 227 '''Explanation of this fileset predicate ....
205 228 '''
206 229 pass
207 230
208 231 The first string argument is used also in online help.
209 232
210 233 Optional argument 'callstatus' indicates whether a predicate
211 234 implies 'matchctx.status()' at runtime or not (False, by
212 235 default).
213 236
214 237 Optional argument 'callexisting' indicates whether a predicate
215 238 implies 'matchctx.existing()' at runtime or not (False, by
216 239 default).
217 240
218 241 'filesetpredicate' instance in example above can be used to
219 242 decorate multiple functions.
220 243
221 244 Decorated functions are registered automatically at loading
222 245 extension, if an instance named as 'filesetpredicate' is used for
223 246 decorating in extension.
224 247
225 248 Otherwise, explicit 'fileset.loadpredicate()' is needed.
226 249 """
227 250 _getname = _funcregistrarbase._parsefuncdecl
228 251 _docformat = "``%s``\n %s"
229 252
230 253 def _extrasetup(self, name, func, callstatus=False, callexisting=False):
231 254 func._callstatus = callstatus
232 255 func._callexisting = callexisting
233 256
234 257 class _templateregistrarbase(_funcregistrarbase):
235 258 """Base of decorator to register functions as template specific one
236 259 """
237 260 _docformat = ":%s: %s"
238 261
239 262 class templatekeyword(_templateregistrarbase):
240 263 """Decorator to register template keyword
241 264
242 265 Usage::
243 266
244 267 templatekeyword = registrar.templatekeyword()
245 268
246 269 @templatekeyword('mykeyword')
247 270 def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
248 271 '''Explanation of this template keyword ....
249 272 '''
250 273 pass
251 274
252 275 The first string argument is used also in online help.
253 276
254 277 'templatekeyword' instance in example above can be used to
255 278 decorate multiple functions.
256 279
257 280 Decorated functions are registered automatically at loading
258 281 extension, if an instance named as 'templatekeyword' is used for
259 282 decorating in extension.
260 283
261 284 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
262 285 """
263 286
264 287 class templatefilter(_templateregistrarbase):
265 288 """Decorator to register template filer
266 289
267 290 Usage::
268 291
269 292 templatefilter = registrar.templatefilter()
270 293
271 294 @templatefilter('myfilter')
272 295 def myfilterfunc(text):
273 296 '''Explanation of this template filter ....
274 297 '''
275 298 pass
276 299
277 300 The first string argument is used also in online help.
278 301
279 302 'templatefilter' instance in example above can be used to
280 303 decorate multiple functions.
281 304
282 305 Decorated functions are registered automatically at loading
283 306 extension, if an instance named as 'templatefilter' is used for
284 307 decorating in extension.
285 308
286 309 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
287 310 """
288 311
289 312 class templatefunc(_templateregistrarbase):
290 313 """Decorator to register template function
291 314
292 315 Usage::
293 316
294 317 templatefunc = registrar.templatefunc()
295 318
296 319 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3')
297 320 def myfuncfunc(context, mapping, args):
298 321 '''Explanation of this template function ....
299 322 '''
300 323 pass
301 324
302 325 The first string argument is used also in online help.
303 326
304 327 If optional 'argspec' is defined, the function will receive 'args' as
305 328 a dict of named arguments. Otherwise 'args' is a list of positional
306 329 arguments.
307 330
308 331 'templatefunc' instance in example above can be used to
309 332 decorate multiple functions.
310 333
311 334 Decorated functions are registered automatically at loading
312 335 extension, if an instance named as 'templatefunc' is used for
313 336 decorating in extension.
314 337
315 338 Otherwise, explicit 'templater.loadfunction()' is needed.
316 339 """
317 340 _getname = _funcregistrarbase._parsefuncdecl
318 341
319 342 def _extrasetup(self, name, func, argspec=None):
320 343 func._argspec = argspec
321 344
322 345 class internalmerge(_funcregistrarbase):
323 346 """Decorator to register in-process merge tool
324 347
325 348 Usage::
326 349
327 350 internalmerge = registrar.internalmerge()
328 351
329 352 @internalmerge('mymerge', internalmerge.mergeonly,
330 353 onfailure=None, precheck=None):
331 354 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
332 355 toolconf, files, labels=None):
333 356 '''Explanation of this internal merge tool ....
334 357 '''
335 358 return 1, False # means "conflicted", "no deletion needed"
336 359
337 360 The first string argument is used to compose actual merge tool name,
338 361 ":name" and "internal:name" (the latter is historical one).
339 362
340 363 The second argument is one of merge types below:
341 364
342 365 ========== ======== ======== =========
343 366 merge type precheck premerge fullmerge
344 367 ========== ======== ======== =========
345 368 nomerge x x x
346 369 mergeonly o x o
347 370 fullmerge o o o
348 371 ========== ======== ======== =========
349 372
350 373 Optional argument 'onfailure' is the format of warning message
351 374 to be used at failure of merging (target filename is specified
352 375 at formatting). Or, None or so, if warning message should be
353 376 suppressed.
354 377
355 378 Optional argument 'precheck' is the function to be used
356 379 before actual invocation of internal merge tool itself.
357 380 It takes as same arguments as internal merge tool does, other than
358 381 'files' and 'labels'. If it returns false value, merging is aborted
359 382 immediately (and file is marked as "unresolved").
360 383
361 384 'internalmerge' instance in example above can be used to
362 385 decorate multiple functions.
363 386
364 387 Decorated functions are registered automatically at loading
365 388 extension, if an instance named as 'internalmerge' is used for
366 389 decorating in extension.
367 390
368 391 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
369 392 """
370 393 _docformat = "``:%s``\n %s"
371 394
372 395 # merge type definitions:
373 396 nomerge = None
374 397 mergeonly = 'mergeonly' # just the full merge, no premerge
375 398 fullmerge = 'fullmerge' # both premerge and merge
376 399
377 400 def _extrasetup(self, name, func, mergetype,
378 401 onfailure=None, precheck=None):
379 402 func.mergetype = mergetype
380 403 func.onfailure = onfailure
381 404 func.precheck = precheck
General Comments 0
You need to be logged in to leave comments. Login now