##// END OF EJS Templates
dispatch: replace mayberepr with shellquote...
Augie Fackler -
r31500:bc315e66 default
parent child Browse files
Show More
@@ -1,930 +1,925 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 atexit
11 11 import difflib
12 12 import errno
13 13 import getopt
14 14 import os
15 15 import pdb
16 16 import re
17 17 import signal
18 18 import sys
19 19 import time
20 20 import traceback
21 21
22 22
23 23 from .i18n import _
24 24
25 25 from . import (
26 26 cmdutil,
27 27 color,
28 28 commands,
29 29 debugcommands,
30 30 demandimport,
31 31 encoding,
32 32 error,
33 33 extensions,
34 34 fancyopts,
35 35 fileset,
36 36 help,
37 37 hg,
38 38 hook,
39 39 profiling,
40 40 pycompat,
41 41 revset,
42 42 scmutil,
43 43 templatefilters,
44 44 templatekw,
45 45 templater,
46 46 ui as uimod,
47 47 util,
48 48 )
49 49
50 50 class request(object):
51 51 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
52 52 ferr=None):
53 53 self.args = args
54 54 self.ui = ui
55 55 self.repo = repo
56 56
57 57 # input/output/error streams
58 58 self.fin = fin
59 59 self.fout = fout
60 60 self.ferr = ferr
61 61
62 62 def run():
63 63 "run the command in sys.argv"
64 64 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
65 65
66 66 def _getsimilar(symbols, value):
67 67 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
68 68 # The cutoff for similarity here is pretty arbitrary. It should
69 69 # probably be investigated and tweaked.
70 70 return [s for s in symbols if sim(s) > 0.6]
71 71
72 72 def _reportsimilar(write, similar):
73 73 if len(similar) == 1:
74 74 write(_("(did you mean %s?)\n") % similar[0])
75 75 elif similar:
76 76 ss = ", ".join(sorted(similar))
77 77 write(_("(did you mean one of %s?)\n") % ss)
78 78
79 79 def _formatparse(write, inst):
80 80 similar = []
81 81 if isinstance(inst, error.UnknownIdentifier):
82 82 # make sure to check fileset first, as revset can invoke fileset
83 83 similar = _getsimilar(inst.symbols, inst.function)
84 84 if len(inst.args) > 1:
85 85 write(_("hg: parse error at %s: %s\n") %
86 86 (inst.args[1], inst.args[0]))
87 87 if (inst.args[0][0] == ' '):
88 88 write(_("unexpected leading whitespace\n"))
89 89 else:
90 90 write(_("hg: parse error: %s\n") % inst.args[0])
91 91 _reportsimilar(write, similar)
92 92 if inst.hint:
93 93 write(_("(%s)\n") % inst.hint)
94 94
95 def _mayberepr(a):
96 if ' ' in a:
97 return encoding.strtolocal(repr(a))
98 return a
99
100 95 def _formatargs(args):
101 return ' '.join(_mayberepr(a) for a in args)
96 return ' '.join(util.shellquote(a) for a in args)
102 97
103 98 def dispatch(req):
104 99 "run the command specified in req.args"
105 100 if req.ferr:
106 101 ferr = req.ferr
107 102 elif req.ui:
108 103 ferr = req.ui.ferr
109 104 else:
110 105 ferr = util.stderr
111 106
112 107 try:
113 108 if not req.ui:
114 109 req.ui = uimod.ui.load()
115 110 if '--traceback' in req.args:
116 111 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
117 112
118 113 # set ui streams from the request
119 114 if req.fin:
120 115 req.ui.fin = req.fin
121 116 if req.fout:
122 117 req.ui.fout = req.fout
123 118 if req.ferr:
124 119 req.ui.ferr = req.ferr
125 120 except error.Abort as inst:
126 121 ferr.write(_("abort: %s\n") % inst)
127 122 if inst.hint:
128 123 ferr.write(_("(%s)\n") % inst.hint)
129 124 return -1
130 125 except error.ParseError as inst:
131 126 _formatparse(ferr.write, inst)
132 127 return -1
133 128
134 129 msg = _formatargs(req.args)
135 130 starttime = util.timer()
136 131 ret = None
137 132 try:
138 133 ret = _runcatch(req)
139 134 except KeyboardInterrupt:
140 135 try:
141 136 req.ui.warn(_("interrupted!\n"))
142 137 except IOError as inst:
143 138 if inst.errno != errno.EPIPE:
144 139 raise
145 140 ret = -1
146 141 finally:
147 142 duration = util.timer() - starttime
148 143 req.ui.flush()
149 144 if req.ui.logblockedtimes:
150 145 req.ui._blockedtimes['command_duration'] = duration * 1000
151 146 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
152 147 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
153 148 msg, ret or 0, duration)
154 149 return ret
155 150
156 151 def _runcatch(req):
157 152 def catchterm(*args):
158 153 raise error.SignalInterrupt
159 154
160 155 ui = req.ui
161 156 try:
162 157 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
163 158 num = getattr(signal, name, None)
164 159 if num:
165 160 signal.signal(num, catchterm)
166 161 except ValueError:
167 162 pass # happens if called in a thread
168 163
169 164 def _runcatchfunc():
170 165 try:
171 166 debugger = 'pdb'
172 167 debugtrace = {
173 168 'pdb' : pdb.set_trace
174 169 }
175 170 debugmortem = {
176 171 'pdb' : pdb.post_mortem
177 172 }
178 173
179 174 # read --config before doing anything else
180 175 # (e.g. to change trust settings for reading .hg/hgrc)
181 176 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
182 177
183 178 if req.repo:
184 179 # copy configs that were passed on the cmdline (--config) to
185 180 # the repo ui
186 181 for sec, name, val in cfgs:
187 182 req.repo.ui.setconfig(sec, name, val, source='--config')
188 183
189 184 # developer config: ui.debugger
190 185 debugger = ui.config("ui", "debugger")
191 186 debugmod = pdb
192 187 if not debugger or ui.plain():
193 188 # if we are in HGPLAIN mode, then disable custom debugging
194 189 debugger = 'pdb'
195 190 elif '--debugger' in req.args:
196 191 # This import can be slow for fancy debuggers, so only
197 192 # do it when absolutely necessary, i.e. when actual
198 193 # debugging has been requested
199 194 with demandimport.deactivated():
200 195 try:
201 196 debugmod = __import__(debugger)
202 197 except ImportError:
203 198 pass # Leave debugmod = pdb
204 199
205 200 debugtrace[debugger] = debugmod.set_trace
206 201 debugmortem[debugger] = debugmod.post_mortem
207 202
208 203 # enter the debugger before command execution
209 204 if '--debugger' in req.args:
210 205 ui.warn(_("entering debugger - "
211 206 "type c to continue starting hg or h for help\n"))
212 207
213 208 if (debugger != 'pdb' and
214 209 debugtrace[debugger] == debugtrace['pdb']):
215 210 ui.warn(_("%s debugger specified "
216 211 "but its module was not found\n") % debugger)
217 212 with demandimport.deactivated():
218 213 debugtrace[debugger]()
219 214 try:
220 215 return _dispatch(req)
221 216 finally:
222 217 ui.flush()
223 218 except: # re-raises
224 219 # enter the debugger when we hit an exception
225 220 if '--debugger' in req.args:
226 221 traceback.print_exc()
227 222 debugmortem[debugger](sys.exc_info()[2])
228 223 ui.traceback()
229 224 raise
230 225
231 226 return callcatch(ui, _runcatchfunc)
232 227
233 228 def callcatch(ui, func):
234 229 """like scmutil.callcatch but handles more high-level exceptions about
235 230 config parsing and commands. besides, use handlecommandexception to handle
236 231 uncaught exceptions.
237 232 """
238 233 try:
239 234 return scmutil.callcatch(ui, func)
240 235 except error.AmbiguousCommand as inst:
241 236 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
242 237 (inst.args[0], " ".join(inst.args[1])))
243 238 except error.CommandError as inst:
244 239 if inst.args[0]:
245 240 ui.pager('help')
246 241 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
247 242 commands.help_(ui, inst.args[0], full=False, command=True)
248 243 else:
249 244 ui.pager('help')
250 245 ui.warn(_("hg: %s\n") % inst.args[1])
251 246 commands.help_(ui, 'shortlist')
252 247 except error.ParseError as inst:
253 248 _formatparse(ui.warn, inst)
254 249 return -1
255 250 except error.UnknownCommand as inst:
256 251 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
257 252 try:
258 253 # check if the command is in a disabled extension
259 254 # (but don't check for extensions themselves)
260 255 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
261 256 ui.warn(nocmdmsg)
262 257 ui.write(formatted)
263 258 except (error.UnknownCommand, error.Abort):
264 259 suggested = False
265 260 if len(inst.args) == 2:
266 261 sim = _getsimilar(inst.args[1], inst.args[0])
267 262 if sim:
268 263 ui.warn(nocmdmsg)
269 264 _reportsimilar(ui.warn, sim)
270 265 suggested = True
271 266 if not suggested:
272 267 ui.pager('help')
273 268 ui.warn(nocmdmsg)
274 269 commands.help_(ui, 'shortlist')
275 270 except IOError:
276 271 raise
277 272 except KeyboardInterrupt:
278 273 raise
279 274 except: # probably re-raises
280 275 if not handlecommandexception(ui):
281 276 raise
282 277
283 278 return -1
284 279
285 280 def aliasargs(fn, givenargs):
286 281 args = getattr(fn, 'args', [])
287 282 if args:
288 283 cmd = ' '.join(map(util.shellquote, args))
289 284
290 285 nums = []
291 286 def replacer(m):
292 287 num = int(m.group(1)) - 1
293 288 nums.append(num)
294 289 if num < len(givenargs):
295 290 return givenargs[num]
296 291 raise error.Abort(_('too few arguments for command alias'))
297 292 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
298 293 givenargs = [x for i, x in enumerate(givenargs)
299 294 if i not in nums]
300 295 args = pycompat.shlexsplit(cmd)
301 296 return args + givenargs
302 297
303 298 def aliasinterpolate(name, args, cmd):
304 299 '''interpolate args into cmd for shell aliases
305 300
306 301 This also handles $0, $@ and "$@".
307 302 '''
308 303 # util.interpolate can't deal with "$@" (with quotes) because it's only
309 304 # built to match prefix + patterns.
310 305 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
311 306 replacemap['$0'] = name
312 307 replacemap['$$'] = '$'
313 308 replacemap['$@'] = ' '.join(args)
314 309 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
315 310 # parameters, separated out into words. Emulate the same behavior here by
316 311 # quoting the arguments individually. POSIX shells will then typically
317 312 # tokenize each argument into exactly one word.
318 313 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
319 314 # escape '\$' for regex
320 315 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
321 316 r = re.compile(regex)
322 317 return r.sub(lambda x: replacemap[x.group()], cmd)
323 318
324 319 class cmdalias(object):
325 320 def __init__(self, name, definition, cmdtable, source):
326 321 self.name = self.cmd = name
327 322 self.cmdname = ''
328 323 self.definition = definition
329 324 self.fn = None
330 325 self.givenargs = []
331 326 self.opts = []
332 327 self.help = ''
333 328 self.badalias = None
334 329 self.unknowncmd = False
335 330 self.source = source
336 331
337 332 try:
338 333 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
339 334 for alias, e in cmdtable.iteritems():
340 335 if e is entry:
341 336 self.cmd = alias
342 337 break
343 338 self.shadows = True
344 339 except error.UnknownCommand:
345 340 self.shadows = False
346 341
347 342 if not self.definition:
348 343 self.badalias = _("no definition for alias '%s'") % self.name
349 344 return
350 345
351 346 if self.definition.startswith('!'):
352 347 self.shell = True
353 348 def fn(ui, *args):
354 349 env = {'HG_ARGS': ' '.join((self.name,) + args)}
355 350 def _checkvar(m):
356 351 if m.groups()[0] == '$':
357 352 return m.group()
358 353 elif int(m.groups()[0]) <= len(args):
359 354 return m.group()
360 355 else:
361 356 ui.debug("No argument found for substitution "
362 357 "of %i variable in alias '%s' definition."
363 358 % (int(m.groups()[0]), self.name))
364 359 return ''
365 360 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
366 361 cmd = aliasinterpolate(self.name, args, cmd)
367 362 return ui.system(cmd, environ=env,
368 363 blockedtag='alias_%s' % self.name)
369 364 self.fn = fn
370 365 return
371 366
372 367 try:
373 368 args = pycompat.shlexsplit(self.definition)
374 369 except ValueError as inst:
375 370 self.badalias = (_("error in definition for alias '%s': %s")
376 371 % (self.name, inst))
377 372 return
378 373 self.cmdname = cmd = args.pop(0)
379 374 self.givenargs = args
380 375
381 376 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
382 377 if _earlygetopt([invalidarg], args):
383 378 self.badalias = (_("error in definition for alias '%s': %s may "
384 379 "only be given on the command line")
385 380 % (self.name, invalidarg))
386 381 return
387 382
388 383 try:
389 384 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
390 385 if len(tableentry) > 2:
391 386 self.fn, self.opts, self.help = tableentry
392 387 else:
393 388 self.fn, self.opts = tableentry
394 389
395 390 if self.help.startswith("hg " + cmd):
396 391 # drop prefix in old-style help lines so hg shows the alias
397 392 self.help = self.help[4 + len(cmd):]
398 393 self.__doc__ = self.fn.__doc__
399 394
400 395 except error.UnknownCommand:
401 396 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
402 397 % (self.name, cmd))
403 398 self.unknowncmd = True
404 399 except error.AmbiguousCommand:
405 400 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
406 401 % (self.name, cmd))
407 402
408 403 @property
409 404 def args(self):
410 405 args = map(util.expandpath, self.givenargs)
411 406 return aliasargs(self.fn, args)
412 407
413 408 def __getattr__(self, name):
414 409 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
415 410 if name not in adefaults:
416 411 raise AttributeError(name)
417 412 if self.badalias or util.safehasattr(self, 'shell'):
418 413 return adefaults[name]
419 414 return getattr(self.fn, name)
420 415
421 416 def __call__(self, ui, *args, **opts):
422 417 if self.badalias:
423 418 hint = None
424 419 if self.unknowncmd:
425 420 try:
426 421 # check if the command is in a disabled extension
427 422 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
428 423 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
429 424 except error.UnknownCommand:
430 425 pass
431 426 raise error.Abort(self.badalias, hint=hint)
432 427 if self.shadows:
433 428 ui.debug("alias '%s' shadows command '%s'\n" %
434 429 (self.name, self.cmdname))
435 430
436 431 ui.log('commandalias', "alias '%s' expands to '%s'\n",
437 432 self.name, self.definition)
438 433 if util.safehasattr(self, 'shell'):
439 434 return self.fn(ui, *args, **opts)
440 435 else:
441 436 try:
442 437 return util.checksignature(self.fn)(ui, *args, **opts)
443 438 except error.SignatureError:
444 439 args = ' '.join([self.cmdname] + self.args)
445 440 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
446 441 raise
447 442
448 443 def addaliases(ui, cmdtable):
449 444 # aliases are processed after extensions have been loaded, so they
450 445 # may use extension commands. Aliases can also use other alias definitions,
451 446 # but only if they have been defined prior to the current definition.
452 447 for alias, definition in ui.configitems('alias'):
453 448 source = ui.configsource('alias', alias)
454 449 aliasdef = cmdalias(alias, definition, cmdtable, source)
455 450
456 451 try:
457 452 olddef = cmdtable[aliasdef.cmd][0]
458 453 if olddef.definition == aliasdef.definition:
459 454 continue
460 455 except (KeyError, AttributeError):
461 456 # definition might not exist or it might not be a cmdalias
462 457 pass
463 458
464 459 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
465 460
466 461 def _parse(ui, args):
467 462 options = {}
468 463 cmdoptions = {}
469 464
470 465 try:
471 466 args = fancyopts.fancyopts(args, commands.globalopts, options)
472 467 except getopt.GetoptError as inst:
473 468 raise error.CommandError(None, inst)
474 469
475 470 if args:
476 471 cmd, args = args[0], args[1:]
477 472 aliases, entry = cmdutil.findcmd(cmd, commands.table,
478 473 ui.configbool("ui", "strict"))
479 474 cmd = aliases[0]
480 475 args = aliasargs(entry[0], args)
481 476 defaults = ui.config("defaults", cmd)
482 477 if defaults:
483 478 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
484 479 c = list(entry[1])
485 480 else:
486 481 cmd = None
487 482 c = []
488 483
489 484 # combine global options into local
490 485 for o in commands.globalopts:
491 486 c.append((o[0], o[1], options[o[1]], o[3]))
492 487
493 488 try:
494 489 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
495 490 except getopt.GetoptError as inst:
496 491 raise error.CommandError(cmd, inst)
497 492
498 493 # separate global options back out
499 494 for o in commands.globalopts:
500 495 n = o[1]
501 496 options[n] = cmdoptions[n]
502 497 del cmdoptions[n]
503 498
504 499 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
505 500
506 501 def _parseconfig(ui, config):
507 502 """parse the --config options from the command line"""
508 503 configs = []
509 504
510 505 for cfg in config:
511 506 try:
512 507 name, value = [cfgelem.strip()
513 508 for cfgelem in cfg.split('=', 1)]
514 509 section, name = name.split('.', 1)
515 510 if not section or not name:
516 511 raise IndexError
517 512 ui.setconfig(section, name, value, '--config')
518 513 configs.append((section, name, value))
519 514 except (IndexError, ValueError):
520 515 raise error.Abort(_('malformed --config option: %r '
521 516 '(use --config section.name=value)') % cfg)
522 517
523 518 return configs
524 519
525 520 def _earlygetopt(aliases, args):
526 521 """Return list of values for an option (or aliases).
527 522
528 523 The values are listed in the order they appear in args.
529 524 The options and values are removed from args.
530 525
531 526 >>> args = ['x', '--cwd', 'foo', 'y']
532 527 >>> _earlygetopt(['--cwd'], args), args
533 528 (['foo'], ['x', 'y'])
534 529
535 530 >>> args = ['x', '--cwd=bar', 'y']
536 531 >>> _earlygetopt(['--cwd'], args), args
537 532 (['bar'], ['x', 'y'])
538 533
539 534 >>> args = ['x', '-R', 'foo', 'y']
540 535 >>> _earlygetopt(['-R'], args), args
541 536 (['foo'], ['x', 'y'])
542 537
543 538 >>> args = ['x', '-Rbar', 'y']
544 539 >>> _earlygetopt(['-R'], args), args
545 540 (['bar'], ['x', 'y'])
546 541 """
547 542 try:
548 543 argcount = args.index("--")
549 544 except ValueError:
550 545 argcount = len(args)
551 546 shortopts = [opt for opt in aliases if len(opt) == 2]
552 547 values = []
553 548 pos = 0
554 549 while pos < argcount:
555 550 fullarg = arg = args[pos]
556 551 equals = arg.find('=')
557 552 if equals > -1:
558 553 arg = arg[:equals]
559 554 if arg in aliases:
560 555 del args[pos]
561 556 if equals > -1:
562 557 values.append(fullarg[equals + 1:])
563 558 argcount -= 1
564 559 else:
565 560 if pos + 1 >= argcount:
566 561 # ignore and let getopt report an error if there is no value
567 562 break
568 563 values.append(args.pop(pos))
569 564 argcount -= 2
570 565 elif arg[:2] in shortopts:
571 566 # short option can have no following space, e.g. hg log -Rfoo
572 567 values.append(args.pop(pos)[2:])
573 568 argcount -= 1
574 569 else:
575 570 pos += 1
576 571 return values
577 572
578 573 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
579 574 # run pre-hook, and abort if it fails
580 575 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
581 576 pats=cmdpats, opts=cmdoptions)
582 577 try:
583 578 ret = _runcommand(ui, options, cmd, d)
584 579 # run post-hook, passing command result
585 580 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
586 581 result=ret, pats=cmdpats, opts=cmdoptions)
587 582 except Exception:
588 583 # run failure hook and re-raise
589 584 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
590 585 pats=cmdpats, opts=cmdoptions)
591 586 raise
592 587 return ret
593 588
594 589 def _getlocal(ui, rpath, wd=None):
595 590 """Return (path, local ui object) for the given target path.
596 591
597 592 Takes paths in [cwd]/.hg/hgrc into account."
598 593 """
599 594 if wd is None:
600 595 try:
601 596 wd = pycompat.getcwd()
602 597 except OSError as e:
603 598 raise error.Abort(_("error getting current working directory: %s") %
604 599 e.strerror)
605 600 path = cmdutil.findrepo(wd) or ""
606 601 if not path:
607 602 lui = ui
608 603 else:
609 604 lui = ui.copy()
610 605 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
611 606
612 607 if rpath and rpath[-1]:
613 608 path = lui.expandpath(rpath[-1])
614 609 lui = ui.copy()
615 610 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
616 611
617 612 return path, lui
618 613
619 614 def _checkshellalias(lui, ui, args):
620 615 """Return the function to run the shell alias, if it is required"""
621 616 options = {}
622 617
623 618 try:
624 619 args = fancyopts.fancyopts(args, commands.globalopts, options)
625 620 except getopt.GetoptError:
626 621 return
627 622
628 623 if not args:
629 624 return
630 625
631 626 cmdtable = commands.table
632 627
633 628 cmd = args[0]
634 629 try:
635 630 strict = ui.configbool("ui", "strict")
636 631 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
637 632 except (error.AmbiguousCommand, error.UnknownCommand):
638 633 return
639 634
640 635 cmd = aliases[0]
641 636 fn = entry[0]
642 637
643 638 if cmd and util.safehasattr(fn, 'shell'):
644 639 d = lambda: fn(ui, *args[1:])
645 640 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
646 641 [], {})
647 642
648 643 _loaded = set()
649 644
650 645 # list of (objname, loadermod, loadername) tuple:
651 646 # - objname is the name of an object in extension module, from which
652 647 # extra information is loaded
653 648 # - loadermod is the module where loader is placed
654 649 # - loadername is the name of the function, which takes (ui, extensionname,
655 650 # extraobj) arguments
656 651 extraloaders = [
657 652 ('cmdtable', commands, 'loadcmdtable'),
658 653 ('colortable', color, 'loadcolortable'),
659 654 ('filesetpredicate', fileset, 'loadpredicate'),
660 655 ('revsetpredicate', revset, 'loadpredicate'),
661 656 ('templatefilter', templatefilters, 'loadfilter'),
662 657 ('templatefunc', templater, 'loadfunction'),
663 658 ('templatekeyword', templatekw, 'loadkeyword'),
664 659 ]
665 660
666 661 def _dispatch(req):
667 662 args = req.args
668 663 ui = req.ui
669 664
670 665 # check for cwd
671 666 cwd = _earlygetopt(['--cwd'], args)
672 667 if cwd:
673 668 os.chdir(cwd[-1])
674 669
675 670 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
676 671 path, lui = _getlocal(ui, rpath)
677 672
678 673 # Side-effect of accessing is debugcommands module is guaranteed to be
679 674 # imported and commands.table is populated.
680 675 debugcommands.command
681 676
682 677 uis = set([ui, lui])
683 678
684 679 if req.repo:
685 680 uis.add(req.repo.ui)
686 681
687 682 if '--profile' in args:
688 683 for ui_ in uis:
689 684 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
690 685
691 686 with profiling.maybeprofile(lui):
692 687 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
693 688 # reposetup. Programs like TortoiseHg will call _dispatch several
694 689 # times so we keep track of configured extensions in _loaded.
695 690 extensions.loadall(lui)
696 691 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
697 692 # Propagate any changes to lui.__class__ by extensions
698 693 ui.__class__ = lui.__class__
699 694
700 695 # (uisetup and extsetup are handled in extensions.loadall)
701 696
702 697 for name, module in exts:
703 698 for objname, loadermod, loadername in extraloaders:
704 699 extraobj = getattr(module, objname, None)
705 700 if extraobj is not None:
706 701 getattr(loadermod, loadername)(ui, name, extraobj)
707 702 _loaded.add(name)
708 703
709 704 # (reposetup is handled in hg.repository)
710 705
711 706 addaliases(lui, commands.table)
712 707
713 708 # All aliases and commands are completely defined, now.
714 709 # Check abbreviation/ambiguity of shell alias.
715 710 shellaliasfn = _checkshellalias(lui, ui, args)
716 711 if shellaliasfn:
717 712 return shellaliasfn()
718 713
719 714 # check for fallback encoding
720 715 fallback = lui.config('ui', 'fallbackencoding')
721 716 if fallback:
722 717 encoding.fallbackencoding = fallback
723 718
724 719 fullargs = args
725 720 cmd, func, args, options, cmdoptions = _parse(lui, args)
726 721
727 722 if options["config"]:
728 723 raise error.Abort(_("option --config may not be abbreviated!"))
729 724 if options["cwd"]:
730 725 raise error.Abort(_("option --cwd may not be abbreviated!"))
731 726 if options["repository"]:
732 727 raise error.Abort(_(
733 728 "option -R has to be separated from other options (e.g. not "
734 729 "-qR) and --repository may only be abbreviated as --repo!"))
735 730
736 731 if options["encoding"]:
737 732 encoding.encoding = options["encoding"]
738 733 if options["encodingmode"]:
739 734 encoding.encodingmode = options["encodingmode"]
740 735 if options["time"]:
741 736 def get_times():
742 737 t = os.times()
743 738 if t[4] == 0.0:
744 739 # Windows leaves this as zero, so use time.clock()
745 740 t = (t[0], t[1], t[2], t[3], time.clock())
746 741 return t
747 742 s = get_times()
748 743 def print_time():
749 744 t = get_times()
750 745 ui.warn(
751 746 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
752 747 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
753 748 atexit.register(print_time)
754 749
755 750 if options['verbose'] or options['debug'] or options['quiet']:
756 751 for opt in ('verbose', 'debug', 'quiet'):
757 752 val = str(bool(options[opt]))
758 753 if pycompat.ispy3:
759 754 val = val.encode('ascii')
760 755 for ui_ in uis:
761 756 ui_.setconfig('ui', opt, val, '--' + opt)
762 757
763 758 if options['traceback']:
764 759 for ui_ in uis:
765 760 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
766 761
767 762 if options['noninteractive']:
768 763 for ui_ in uis:
769 764 ui_.setconfig('ui', 'interactive', 'off', '-y')
770 765
771 766 if util.parsebool(options['pager']):
772 767 ui.pager('internal-always-' + cmd)
773 768 elif options['pager'] != 'auto':
774 769 ui.disablepager()
775 770
776 771 if cmdoptions.get('insecure', False):
777 772 for ui_ in uis:
778 773 ui_.insecureconnections = True
779 774
780 775 # setup color handling
781 776 coloropt = options['color']
782 777 for ui_ in uis:
783 778 if coloropt:
784 779 ui_.setconfig('ui', 'color', coloropt, '--color')
785 780 color.setup(ui_)
786 781
787 782 if options['version']:
788 783 return commands.version_(ui)
789 784 if options['help']:
790 785 return commands.help_(ui, cmd, command=cmd is not None)
791 786 elif not cmd:
792 787 return commands.help_(ui, 'shortlist')
793 788
794 789 repo = None
795 790 cmdpats = args[:]
796 791 if not func.norepo:
797 792 # use the repo from the request only if we don't have -R
798 793 if not rpath and not cwd:
799 794 repo = req.repo
800 795
801 796 if repo:
802 797 # set the descriptors of the repo ui to those of ui
803 798 repo.ui.fin = ui.fin
804 799 repo.ui.fout = ui.fout
805 800 repo.ui.ferr = ui.ferr
806 801 else:
807 802 try:
808 803 repo = hg.repository(ui, path=path)
809 804 if not repo.local():
810 805 raise error.Abort(_("repository '%s' is not local")
811 806 % path)
812 807 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
813 808 'repo')
814 809 except error.RequirementError:
815 810 raise
816 811 except error.RepoError:
817 812 if rpath and rpath[-1]: # invalid -R path
818 813 raise
819 814 if not func.optionalrepo:
820 815 if func.inferrepo and args and not path:
821 816 # try to infer -R from command args
822 817 repos = map(cmdutil.findrepo, args)
823 818 guess = repos[0]
824 819 if guess and repos.count(guess) == len(repos):
825 820 req.args = ['--repository', guess] + fullargs
826 821 return _dispatch(req)
827 822 if not path:
828 823 raise error.RepoError(_("no repository found in"
829 824 " '%s' (.hg not found)")
830 825 % pycompat.getcwd())
831 826 raise
832 827 if repo:
833 828 ui = repo.ui
834 829 if options['hidden']:
835 830 repo = repo.unfiltered()
836 831 args.insert(0, repo)
837 832 elif rpath:
838 833 ui.warn(_("warning: --repository ignored\n"))
839 834
840 835 msg = _formatargs(fullargs)
841 836 ui.log("command", '%s\n', msg)
842 837 strcmdopt = pycompat.strkwargs(cmdoptions)
843 838 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
844 839 try:
845 840 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
846 841 cmdpats, cmdoptions)
847 842 finally:
848 843 if repo and repo != req.repo:
849 844 repo.close()
850 845
851 846 def _runcommand(ui, options, cmd, cmdfunc):
852 847 """Run a command function, possibly with profiling enabled."""
853 848 try:
854 849 return cmdfunc()
855 850 except error.SignatureError:
856 851 raise error.CommandError(cmd, _('invalid arguments'))
857 852
858 853 def _exceptionwarning(ui):
859 854 """Produce a warning message for the current active exception"""
860 855
861 856 # For compatibility checking, we discard the portion of the hg
862 857 # version after the + on the assumption that if a "normal
863 858 # user" is running a build with a + in it the packager
864 859 # probably built from fairly close to a tag and anyone with a
865 860 # 'make local' copy of hg (where the version number can be out
866 861 # of date) will be clueful enough to notice the implausible
867 862 # version number and try updating.
868 863 ct = util.versiontuple(n=2)
869 864 worst = None, ct, ''
870 865 if ui.config('ui', 'supportcontact', None) is None:
871 866 for name, mod in extensions.extensions():
872 867 testedwith = getattr(mod, 'testedwith', '')
873 868 if pycompat.ispy3 and isinstance(testedwith, str):
874 869 testedwith = testedwith.encode(u'utf-8')
875 870 report = getattr(mod, 'buglink', _('the extension author.'))
876 871 if not testedwith.strip():
877 872 # We found an untested extension. It's likely the culprit.
878 873 worst = name, 'unknown', report
879 874 break
880 875
881 876 # Never blame on extensions bundled with Mercurial.
882 877 if extensions.ismoduleinternal(mod):
883 878 continue
884 879
885 880 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
886 881 if ct in tested:
887 882 continue
888 883
889 884 lower = [t for t in tested if t < ct]
890 885 nearest = max(lower or tested)
891 886 if worst[0] is None or nearest < worst[1]:
892 887 worst = name, nearest, report
893 888 if worst[0] is not None:
894 889 name, testedwith, report = worst
895 890 if not isinstance(testedwith, (bytes, str)):
896 891 testedwith = '.'.join([str(c) for c in testedwith])
897 892 warning = (_('** Unknown exception encountered with '
898 893 'possibly-broken third-party extension %s\n'
899 894 '** which supports versions %s of Mercurial.\n'
900 895 '** Please disable %s and try your action again.\n'
901 896 '** If that fixes the bug please report it to %s\n')
902 897 % (name, testedwith, name, report))
903 898 else:
904 899 bugtracker = ui.config('ui', 'supportcontact', None)
905 900 if bugtracker is None:
906 901 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
907 902 warning = (_("** unknown exception encountered, "
908 903 "please report by visiting\n** ") + bugtracker + '\n')
909 904 if pycompat.ispy3:
910 905 sysversion = sys.version.encode(u'utf-8')
911 906 else:
912 907 sysversion = sys.version
913 908 sysversion = sysversion.replace('\n', '')
914 909 warning += ((_("** Python %s\n") % sysversion) +
915 910 (_("** Mercurial Distributed SCM (version %s)\n") %
916 911 util.version()) +
917 912 (_("** Extensions loaded: %s\n") %
918 913 ", ".join([x[0] for x in extensions.extensions()])))
919 914 return warning
920 915
921 916 def handlecommandexception(ui):
922 917 """Produce a warning message for broken commands
923 918
924 919 Called when handling an exception; the exception is reraised if
925 920 this function returns False, ignored otherwise.
926 921 """
927 922 warning = _exceptionwarning(ui)
928 923 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
929 924 ui.warn(warning)
930 925 return False # re-raise the exception
@@ -1,223 +1,223 b''
1 1 setup
2 2 $ cat >> $HGRCPATH <<EOF
3 3 > [extensions]
4 4 > blackbox=
5 5 > mock=$TESTDIR/mockblackbox.py
6 6 > mq=
7 7 > [alias]
8 8 > confuse = log --limit 3
9 9 > EOF
10 10 $ hg init blackboxtest
11 11 $ cd blackboxtest
12 12
13 13 command, exit codes, and duration
14 14
15 15 $ echo a > a
16 16 $ hg add a
17 17 $ hg blackbox --config blackbox.dirty=True
18 18 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
19 19 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
20 20 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox
21 21
22 22 alias expansion is logged
23 23 $ hg confuse
24 24 $ hg blackbox
25 25 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
26 26 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
27 27 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox
28 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config blackbox.dirty=True exited 0 after * seconds (glob)
28 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config 'blackbox.dirty=True' exited 0 after * seconds (glob)
29 29 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse
30 30 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
31 31 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob)
32 32 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
33 33
34 34 incoming change tracking
35 35
36 36 create two heads to verify that we only see one change in the log later
37 37 $ hg commit -ma
38 38 $ hg up null
39 39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 40 $ echo b > b
41 41 $ hg commit -Amb
42 42 adding b
43 43 created new head
44 44
45 45 clone, commit, pull
46 46 $ hg clone . ../blackboxtest2
47 47 updating to branch default
48 48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 49 $ echo c > c
50 50 $ hg commit -Amc
51 51 adding c
52 52 $ cd ../blackboxtest2
53 53 $ hg pull
54 54 pulling from $TESTTMP/blackboxtest (glob)
55 55 searching for changes
56 56 adding changesets
57 57 adding manifests
58 58 adding file changes
59 59 added 1 changesets with 1 changes to 1 files
60 60 (run 'hg update' to get a working copy)
61 61 $ hg blackbox -l 6
62 62 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull
63 63 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated served branch cache in * seconds (glob)
64 64 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote served branch cache with 1 labels and 2 nodes
65 65 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> 1 incoming changes - new heads: d02f48003e62
66 66 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull exited 0 after * seconds (glob)
67 67 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
68 68
69 69 we must not cause a failure if we cannot write to the log
70 70
71 71 $ hg rollback
72 72 repository tip rolled back to revision 1 (undo pull)
73 73
74 74 $ mv .hg/blackbox.log .hg/blackbox.log-
75 75 $ mkdir .hg/blackbox.log
76 76 $ hg --debug incoming
77 77 warning: cannot write to blackbox.log: * (glob)
78 78 comparing with $TESTTMP/blackboxtest (glob)
79 79 query 1; heads
80 80 searching for changes
81 81 all local heads known remotely
82 82 changeset: 2:d02f48003e62c24e2659d97d30f2a83abe5d5d51
83 83 tag: tip
84 84 phase: draft
85 85 parent: 1:6563da9dcf87b1949716e38ff3e3dfaa3198eb06
86 86 parent: -1:0000000000000000000000000000000000000000
87 87 manifest: 2:ab9d46b053ebf45b7996f2922b9893ff4b63d892
88 88 user: test
89 89 date: Thu Jan 01 00:00:00 1970 +0000
90 90 files+: c
91 91 extra: branch=default
92 92 description:
93 93 c
94 94
95 95
96 96 $ hg pull
97 97 pulling from $TESTTMP/blackboxtest (glob)
98 98 searching for changes
99 99 adding changesets
100 100 adding manifests
101 101 adding file changes
102 102 added 1 changesets with 1 changes to 1 files
103 103 (run 'hg update' to get a working copy)
104 104
105 105 a failure reading from the log is fatal
106 106
107 107 $ hg blackbox -l 3
108 108 abort: *$TESTTMP/blackboxtest2/.hg/blackbox.log* (glob)
109 109 [255]
110 110
111 111 $ rmdir .hg/blackbox.log
112 112 $ mv .hg/blackbox.log- .hg/blackbox.log
113 113
114 114 backup bundles get logged
115 115
116 116 $ touch d
117 117 $ hg commit -Amd
118 118 adding d
119 119 created new head
120 120 $ hg strip tip
121 121 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
122 122 saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
123 123 $ hg blackbox -l 6
124 124 1970/01/01 00:00:00 bob @73f6ee326b27d820b0472f1a825e3a50f3dc489b (5000)> strip tip
125 125 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/73f6ee326b27-7612e004-backup.hg (glob)
126 126 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated base branch cache in * seconds (glob)
127 127 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote base branch cache with 1 labels and 2 nodes
128 128 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> strip tip exited 0 after * seconds (glob)
129 129 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
130 130
131 131 extension and python hooks - use the eol extension for a pythonhook
132 132
133 133 $ echo '[extensions]' >> .hg/hgrc
134 134 $ echo 'eol=' >> .hg/hgrc
135 135 $ echo '[hooks]' >> .hg/hgrc
136 136 $ echo 'update = echo hooked' >> .hg/hgrc
137 137 $ hg update
138 138 hooked
139 139 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 140 1 other heads for branch "default"
141 141 $ hg blackbox -l 6
142 142 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update
143 143 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> writing .hg/cache/tags2-visible with 0 tags
144 144 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
145 145 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob)
146 146 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob)
147 147 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 6
148 148
149 149 log rotation
150 150
151 151 $ echo '[blackbox]' >> .hg/hgrc
152 152 $ echo 'maxsize = 20 b' >> .hg/hgrc
153 153 $ echo 'maxfiles = 3' >> .hg/hgrc
154 154 $ hg status
155 155 $ hg status
156 156 $ hg status
157 157 $ hg tip -q
158 158 2:d02f48003e62
159 159 $ ls .hg/blackbox.log*
160 160 .hg/blackbox.log
161 161 .hg/blackbox.log.1
162 162 .hg/blackbox.log.2
163 163 $ cd ..
164 164
165 165 $ hg init blackboxtest3
166 166 $ cd blackboxtest3
167 167 $ hg blackbox
168 168 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
169 169 $ mv .hg/blackbox.log .hg/blackbox.log-
170 170 $ mkdir .hg/blackbox.log
171 171 $ sed -e 's/\(.*test1.*\)/#\1/; s#\(.*commit2.*\)#os.rmdir(".hg/blackbox.log")\
172 172 > os.rename(".hg/blackbox.log-", ".hg/blackbox.log")\
173 173 > \1#' $TESTDIR/test-dispatch.py > ../test-dispatch.py
174 174 $ python $TESTDIR/blackbox-readonly-dispatch.py
175 175 running: add foo
176 176 result: 0
177 177 running: commit -m commit1 -d 2000-01-01 foo
178 178 result: None
179 179 running: commit -m commit2 -d 2000-01-02 foo
180 180 result: None
181 181 running: log -r 0
182 182 changeset: 0:0e4634943879
183 183 user: test
184 184 date: Sat Jan 01 00:00:00 2000 +0000
185 185 summary: commit1
186 186
187 187 result: None
188 188 running: log -r tip
189 189 changeset: 1:45589e459b2e
190 190 tag: tip
191 191 user: test
192 192 date: Sun Jan 02 00:00:00 2000 +0000
193 193 summary: commit2
194 194
195 195 result: None
196 196 $ hg blackbox
197 197 1970/01/01 00:00:00 bob @0e46349438790c460c5c9f7546bfcd39b267bbd2 (5000)> commit -m commit2 -d 2000-01-02 foo
198 198 1970/01/01 00:00:00 bob @0e46349438790c460c5c9f7546bfcd39b267bbd2 (5000)> updated served branch cache in * seconds (glob)
199 199 1970/01/01 00:00:00 bob @0e46349438790c460c5c9f7546bfcd39b267bbd2 (5000)> wrote served branch cache with 1 labels and 1 nodes
200 200 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> commit -m commit2 -d 2000-01-02 foo exited 0 after * seconds (glob)
201 201 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r 0
202 202 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> writing .hg/cache/tags2-visible with 0 tags
203 203 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r 0 exited 0 after * seconds (glob)
204 204 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r tip
205 205 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r tip exited 0 after * seconds (glob)
206 206 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> blackbox
207 207
208 208 Test log recursion from dirty status check
209 209
210 210 $ cat > ../r.py <<EOF
211 211 > from mercurial import context, error, extensions
212 212 > x=[False]
213 213 > def status(orig, *args, **opts):
214 214 > args[0].repo().ui.log("broken", "recursion?")
215 215 > return orig(*args, **opts)
216 216 > def reposetup(ui, repo):
217 217 > extensions.wrapfunction(context.basectx, 'status', status)
218 218 > EOF
219 219 $ hg id --config extensions.x=../r.py --config blackbox.dirty=True
220 220 45589e459b2e tip
221 221
222 222 cleanup
223 223 $ cd ..
@@ -1,179 +1,179 b''
1 1
2 2 $ cat << EOF > buggylocking.py
3 3 > """A small extension that tests our developer warnings
4 4 > """
5 5 >
6 6 > from mercurial import cmdutil, repair, revset
7 7 >
8 8 > cmdtable = {}
9 9 > command = cmdutil.command(cmdtable)
10 10 >
11 11 > @command('buggylocking', [], '')
12 12 > def buggylocking(ui, repo):
13 13 > lo = repo.lock()
14 14 > wl = repo.wlock()
15 15 > wl.release()
16 16 > lo.release()
17 17 >
18 18 > @command('buggytransaction', [], '')
19 19 > def buggylocking(ui, repo):
20 20 > tr = repo.transaction('buggy')
21 21 > # make sure we rollback the transaction as we don't want to rely on the__del__
22 22 > tr.release()
23 23 >
24 24 > @command('properlocking', [], '')
25 25 > def properlocking(ui, repo):
26 26 > """check that reentrance is fine"""
27 27 > wl = repo.wlock()
28 28 > lo = repo.lock()
29 29 > tr = repo.transaction('proper')
30 30 > tr2 = repo.transaction('proper')
31 31 > lo2 = repo.lock()
32 32 > wl2 = repo.wlock()
33 33 > wl2.release()
34 34 > lo2.release()
35 35 > tr2.close()
36 36 > tr.close()
37 37 > lo.release()
38 38 > wl.release()
39 39 >
40 40 > @command('nowaitlocking', [], '')
41 41 > def nowaitlocking(ui, repo):
42 42 > lo = repo.lock()
43 43 > wl = repo.wlock(wait=False)
44 44 > wl.release()
45 45 > lo.release()
46 46 >
47 47 > @command('stripintr', [], '')
48 48 > def stripintr(ui, repo):
49 49 > lo = repo.lock()
50 50 > tr = repo.transaction('foobar')
51 51 > try:
52 52 > repair.strip(repo.ui, repo, [repo['.'].node()])
53 53 > finally:
54 54 > lo.release()
55 55 > @command('oldanddeprecated', [], '')
56 56 > def oldanddeprecated(ui, repo):
57 57 > """test deprecation warning API"""
58 58 > def foobar(ui):
59 59 > ui.deprecwarn('foorbar is deprecated, go shopping', '42.1337')
60 60 > foobar(ui)
61 61 >
62 62 > def oldstylerevset(repo, subset, x):
63 63 > return list(subset)
64 64 >
65 65 > revset.symbols['oldstyle'] = oldstylerevset
66 66 > EOF
67 67
68 68 $ cat << EOF >> $HGRCPATH
69 69 > [extensions]
70 70 > buggylocking=$TESTTMP/buggylocking.py
71 71 > mock=$TESTDIR/mockblackbox.py
72 72 > blackbox=
73 73 > [devel]
74 74 > all-warnings=1
75 75 > EOF
76 76
77 77 $ hg init lock-checker
78 78 $ cd lock-checker
79 79 $ hg buggylocking
80 80 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
81 81 $ cat << EOF >> $HGRCPATH
82 82 > [devel]
83 83 > all=0
84 84 > check-locks=1
85 85 > EOF
86 86 $ hg buggylocking
87 87 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
88 88 $ hg buggylocking --traceback
89 89 devel-warn: "wlock" acquired after "lock" at:
90 90 */hg:* in * (glob)
91 91 */mercurial/dispatch.py:* in run (glob)
92 92 */mercurial/dispatch.py:* in dispatch (glob)
93 93 */mercurial/dispatch.py:* in _runcatch (glob)
94 94 */mercurial/dispatch.py:* in callcatch (glob)
95 95 */mercurial/scmutil.py* in callcatch (glob)
96 96 */mercurial/dispatch.py:* in _runcatchfunc (glob)
97 97 */mercurial/dispatch.py:* in _dispatch (glob)
98 98 */mercurial/dispatch.py:* in runcommand (glob)
99 99 */mercurial/dispatch.py:* in _runcommand (glob)
100 100 */mercurial/dispatch.py:* in <lambda> (glob)
101 101 */mercurial/util.py:* in check (glob)
102 102 $TESTTMP/buggylocking.py:* in buggylocking (glob)
103 103 $ hg properlocking
104 104 $ hg nowaitlocking
105 105
106 106 $ echo a > a
107 107 $ hg add a
108 108 $ hg commit -m a
109 109 $ hg stripintr
110 110 saved backup bundle to $TESTTMP/lock-checker/.hg/strip-backup/*-backup.hg (glob)
111 111 abort: programming error: cannot strip from inside a transaction
112 112 (contact your extension maintainer)
113 113 [255]
114 114
115 115 $ hg log -r "oldstyle()" -T '{rev}\n'
116 116 devel-warn: revset "oldstyle" uses list instead of smartset
117 117 (compatibility will be dropped after Mercurial-3.9, update your code.) at: *mercurial/revset.py:* (mfunc) (glob)
118 118 0
119 119 $ hg oldanddeprecated
120 120 devel-warn: foorbar is deprecated, go shopping
121 121 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
122 122
123 123 $ hg oldanddeprecated --traceback
124 124 devel-warn: foorbar is deprecated, go shopping
125 125 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
126 126 */hg:* in <module> (glob)
127 127 */mercurial/dispatch.py:* in run (glob)
128 128 */mercurial/dispatch.py:* in dispatch (glob)
129 129 */mercurial/dispatch.py:* in _runcatch (glob)
130 130 */mercurial/dispatch.py:* in callcatch (glob)
131 131 */mercurial/scmutil.py* in callcatch (glob)
132 132 */mercurial/dispatch.py:* in _runcatchfunc (glob)
133 133 */mercurial/dispatch.py:* in _dispatch (glob)
134 134 */mercurial/dispatch.py:* in runcommand (glob)
135 135 */mercurial/dispatch.py:* in _runcommand (glob)
136 136 */mercurial/dispatch.py:* in <lambda> (glob)
137 137 */mercurial/util.py:* in check (glob)
138 138 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
139 139 $ hg blackbox -l 9
140 140 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: revset "oldstyle" uses list instead of smartset
141 141 (compatibility will be dropped after Mercurial-3.9, update your code.) at: *mercurial/revset.py:* (mfunc) (glob)
142 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> log -r oldstyle() -T {rev}\n exited 0 after * seconds (glob)
142 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> log -r 'oldstyle()' -T '{rev}\n' exited 0 after * seconds (glob)
143 143 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
144 144 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
145 145 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
146 146 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
147 147 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
148 148 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
149 149 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
150 150 */hg:* in <module> (glob)
151 151 */mercurial/dispatch.py:* in run (glob)
152 152 */mercurial/dispatch.py:* in dispatch (glob)
153 153 */mercurial/dispatch.py:* in _runcatch (glob)
154 154 */mercurial/dispatch.py:* in callcatch (glob)
155 155 */mercurial/scmutil.py* in callcatch (glob)
156 156 */mercurial/dispatch.py:* in _runcatchfunc (glob)
157 157 */mercurial/dispatch.py:* in _dispatch (glob)
158 158 */mercurial/dispatch.py:* in runcommand (glob)
159 159 */mercurial/dispatch.py:* in _runcommand (glob)
160 160 */mercurial/dispatch.py:* in <lambda> (glob)
161 161 */mercurial/util.py:* in check (glob)
162 162 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
163 163 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
164 164 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 9
165 165
166 166 Test programming error failure:
167 167
168 168 $ hg buggytransaction 2>&1 | egrep -v '^ '
169 169 ** Unknown exception encountered with possibly-broken third-party extension buggylocking
170 170 ** which supports versions unknown of Mercurial.
171 171 ** Please disable buggylocking and try your action again.
172 172 ** If that fixes the bug please report it to the extension author.
173 173 ** Python * (glob)
174 174 ** Mercurial Distributed SCM (*) (glob)
175 175 ** Extensions loaded: * (glob)
176 176 Traceback (most recent call last):
177 177 mercurial.error.ProgrammingError: transaction requires locking
178 178
179 179 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now