##// END OF EJS Templates
dispatch: mask negative exit code recorded in blackbox log...
Yuya Nishihara -
r38043:c3fd9a0f default
parent child Browse files
Show More
@@ -1,1056 +1,1056 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 38 scmutil,
39 39 ui as uimod,
40 40 util,
41 41 )
42 42
43 43 from .utils import (
44 44 procutil,
45 45 stringutil,
46 46 )
47 47
48 48 class request(object):
49 49 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
50 50 ferr=None, prereposetups=None):
51 51 self.args = args
52 52 self.ui = ui
53 53 self.repo = repo
54 54
55 55 # input/output/error streams
56 56 self.fin = fin
57 57 self.fout = fout
58 58 self.ferr = ferr
59 59
60 60 # remember options pre-parsed by _earlyparseopts()
61 61 self.earlyoptions = {}
62 62
63 63 # reposetups which run before extensions, useful for chg to pre-fill
64 64 # low-level repo state (for example, changelog) before extensions.
65 65 self.prereposetups = prereposetups or []
66 66
67 67 def _runexithandlers(self):
68 68 exc = None
69 69 handlers = self.ui._exithandlers
70 70 try:
71 71 while handlers:
72 72 func, args, kwargs = handlers.pop()
73 73 try:
74 74 func(*args, **kwargs)
75 75 except: # re-raises below
76 76 if exc is None:
77 77 exc = sys.exc_info()[1]
78 78 self.ui.warn(('error in exit handlers:\n'))
79 79 self.ui.traceback(force=True)
80 80 finally:
81 81 if exc is not None:
82 82 raise exc
83 83
84 84 def run():
85 85 "run the command in sys.argv"
86 86 initstdio()
87 87 req = request(pycompat.sysargv[1:])
88 88 err = None
89 89 try:
90 90 status = dispatch(req)
91 91 except error.StdioError as e:
92 92 err = e
93 93 status = -1
94 94
95 95 # In all cases we try to flush stdio streams.
96 96 if util.safehasattr(req.ui, 'fout'):
97 97 try:
98 98 req.ui.fout.flush()
99 99 except IOError as e:
100 100 err = e
101 101 status = -1
102 102
103 103 if util.safehasattr(req.ui, 'ferr'):
104 104 try:
105 105 if err is not None and err.errno != errno.EPIPE:
106 106 req.ui.ferr.write('abort: %s\n' %
107 107 encoding.strtolocal(err.strerror))
108 108 req.ui.ferr.flush()
109 109 # There's not much we can do about an I/O error here. So (possibly)
110 110 # change the status code and move on.
111 111 except IOError:
112 112 status = -1
113 113
114 114 _silencestdio()
115 115 sys.exit(status & 255)
116 116
117 117 if pycompat.ispy3:
118 118 def initstdio():
119 119 pass
120 120
121 121 def _silencestdio():
122 122 for fp in (sys.stdout, sys.stderr):
123 123 # Check if the file is okay
124 124 try:
125 125 fp.flush()
126 126 continue
127 127 except IOError:
128 128 pass
129 129 # Otherwise mark it as closed to silence "Exception ignored in"
130 130 # message emitted by the interpreter finalizer. Be careful to
131 131 # not close procutil.stdout, which may be a fdopen-ed file object
132 132 # and its close() actually closes the underlying file descriptor.
133 133 try:
134 134 fp.close()
135 135 except IOError:
136 136 pass
137 137 else:
138 138 def initstdio():
139 139 for fp in (sys.stdin, sys.stdout, sys.stderr):
140 140 procutil.setbinary(fp)
141 141
142 142 def _silencestdio():
143 143 pass
144 144
145 145 def _getsimilar(symbols, value):
146 146 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
147 147 # The cutoff for similarity here is pretty arbitrary. It should
148 148 # probably be investigated and tweaked.
149 149 return [s for s in symbols if sim(s) > 0.6]
150 150
151 151 def _reportsimilar(write, similar):
152 152 if len(similar) == 1:
153 153 write(_("(did you mean %s?)\n") % similar[0])
154 154 elif similar:
155 155 ss = ", ".join(sorted(similar))
156 156 write(_("(did you mean one of %s?)\n") % ss)
157 157
158 158 def _formatparse(write, inst):
159 159 similar = []
160 160 if isinstance(inst, error.UnknownIdentifier):
161 161 # make sure to check fileset first, as revset can invoke fileset
162 162 similar = _getsimilar(inst.symbols, inst.function)
163 163 if len(inst.args) > 1:
164 164 write(_("hg: parse error at %s: %s\n") %
165 165 (pycompat.bytestr(inst.args[1]), inst.args[0]))
166 166 if inst.args[0].startswith(' '):
167 167 write(_("unexpected leading whitespace\n"))
168 168 else:
169 169 write(_("hg: parse error: %s\n") % inst.args[0])
170 170 _reportsimilar(write, similar)
171 171 if inst.hint:
172 172 write(_("(%s)\n") % inst.hint)
173 173
174 174 def _formatargs(args):
175 175 return ' '.join(procutil.shellquote(a) for a in args)
176 176
177 177 def dispatch(req):
178 178 """run the command specified in req.args; returns an integer status code"""
179 179 if req.ferr:
180 180 ferr = req.ferr
181 181 elif req.ui:
182 182 ferr = req.ui.ferr
183 183 else:
184 184 ferr = procutil.stderr
185 185
186 186 try:
187 187 if not req.ui:
188 188 req.ui = uimod.ui.load()
189 189 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
190 190 if req.earlyoptions['traceback']:
191 191 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
192 192
193 193 # set ui streams from the request
194 194 if req.fin:
195 195 req.ui.fin = req.fin
196 196 if req.fout:
197 197 req.ui.fout = req.fout
198 198 if req.ferr:
199 199 req.ui.ferr = req.ferr
200 200 except error.Abort as inst:
201 201 ferr.write(_("abort: %s\n") % inst)
202 202 if inst.hint:
203 203 ferr.write(_("(%s)\n") % inst.hint)
204 204 return -1
205 205 except error.ParseError as inst:
206 206 _formatparse(ferr.write, inst)
207 207 return -1
208 208
209 209 msg = _formatargs(req.args)
210 210 starttime = util.timer()
211 211 ret = 1 # default of Python exit code on unhandled exception
212 212 try:
213 213 ret = _runcatch(req) or 0
214 214 except error.ProgrammingError as inst:
215 215 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
216 216 if inst.hint:
217 217 req.ui.warn(_('** (%s)\n') % inst.hint)
218 218 raise
219 219 except KeyboardInterrupt as inst:
220 220 try:
221 221 if isinstance(inst, error.SignalInterrupt):
222 222 msg = _("killed!\n")
223 223 else:
224 224 msg = _("interrupted!\n")
225 225 req.ui.warn(msg)
226 226 except error.SignalInterrupt:
227 227 # maybe pager would quit without consuming all the output, and
228 228 # SIGPIPE was raised. we cannot print anything in this case.
229 229 pass
230 230 except IOError as inst:
231 231 if inst.errno != errno.EPIPE:
232 232 raise
233 233 ret = -1
234 234 finally:
235 235 duration = util.timer() - starttime
236 236 req.ui.flush()
237 237 if req.ui.logblockedtimes:
238 238 req.ui._blockedtimes['command_duration'] = duration * 1000
239 239 req.ui.log('uiblocked', 'ui blocked ms',
240 240 **pycompat.strkwargs(req.ui._blockedtimes))
241 241 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
242 msg, ret, duration)
242 msg, ret & 255, duration)
243 243 try:
244 244 req._runexithandlers()
245 245 except: # exiting, so no re-raises
246 246 ret = ret or -1
247 247 return ret
248 248
249 249 def _runcatch(req):
250 250 def catchterm(*args):
251 251 raise error.SignalInterrupt
252 252
253 253 ui = req.ui
254 254 try:
255 255 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
256 256 num = getattr(signal, name, None)
257 257 if num:
258 258 signal.signal(num, catchterm)
259 259 except ValueError:
260 260 pass # happens if called in a thread
261 261
262 262 def _runcatchfunc():
263 263 realcmd = None
264 264 try:
265 265 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
266 266 cmd = cmdargs[0]
267 267 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
268 268 realcmd = aliases[0]
269 269 except (error.UnknownCommand, error.AmbiguousCommand,
270 270 IndexError, getopt.GetoptError):
271 271 # Don't handle this here. We know the command is
272 272 # invalid, but all we're worried about for now is that
273 273 # it's not a command that server operators expect to
274 274 # be safe to offer to users in a sandbox.
275 275 pass
276 276 if realcmd == 'serve' and '--stdio' in cmdargs:
277 277 # We want to constrain 'hg serve --stdio' instances pretty
278 278 # closely, as many shared-ssh access tools want to grant
279 279 # access to run *only* 'hg -R $repo serve --stdio'. We
280 280 # restrict to exactly that set of arguments, and prohibit
281 281 # any repo name that starts with '--' to prevent
282 282 # shenanigans wherein a user does something like pass
283 283 # --debugger or --config=ui.debugger=1 as a repo
284 284 # name. This used to actually run the debugger.
285 285 if (len(req.args) != 4 or
286 286 req.args[0] != '-R' or
287 287 req.args[1].startswith('--') or
288 288 req.args[2] != 'serve' or
289 289 req.args[3] != '--stdio'):
290 290 raise error.Abort(
291 291 _('potentially unsafe serve --stdio invocation: %r') %
292 292 (req.args,))
293 293
294 294 try:
295 295 debugger = 'pdb'
296 296 debugtrace = {
297 297 'pdb': pdb.set_trace
298 298 }
299 299 debugmortem = {
300 300 'pdb': pdb.post_mortem
301 301 }
302 302
303 303 # read --config before doing anything else
304 304 # (e.g. to change trust settings for reading .hg/hgrc)
305 305 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
306 306
307 307 if req.repo:
308 308 # copy configs that were passed on the cmdline (--config) to
309 309 # the repo ui
310 310 for sec, name, val in cfgs:
311 311 req.repo.ui.setconfig(sec, name, val, source='--config')
312 312
313 313 # developer config: ui.debugger
314 314 debugger = ui.config("ui", "debugger")
315 315 debugmod = pdb
316 316 if not debugger or ui.plain():
317 317 # if we are in HGPLAIN mode, then disable custom debugging
318 318 debugger = 'pdb'
319 319 elif req.earlyoptions['debugger']:
320 320 # This import can be slow for fancy debuggers, so only
321 321 # do it when absolutely necessary, i.e. when actual
322 322 # debugging has been requested
323 323 with demandimport.deactivated():
324 324 try:
325 325 debugmod = __import__(debugger)
326 326 except ImportError:
327 327 pass # Leave debugmod = pdb
328 328
329 329 debugtrace[debugger] = debugmod.set_trace
330 330 debugmortem[debugger] = debugmod.post_mortem
331 331
332 332 # enter the debugger before command execution
333 333 if req.earlyoptions['debugger']:
334 334 ui.warn(_("entering debugger - "
335 335 "type c to continue starting hg or h for help\n"))
336 336
337 337 if (debugger != 'pdb' and
338 338 debugtrace[debugger] == debugtrace['pdb']):
339 339 ui.warn(_("%s debugger specified "
340 340 "but its module was not found\n") % debugger)
341 341 with demandimport.deactivated():
342 342 debugtrace[debugger]()
343 343 try:
344 344 return _dispatch(req)
345 345 finally:
346 346 ui.flush()
347 347 except: # re-raises
348 348 # enter the debugger when we hit an exception
349 349 if req.earlyoptions['debugger']:
350 350 traceback.print_exc()
351 351 debugmortem[debugger](sys.exc_info()[2])
352 352 raise
353 353
354 354 return _callcatch(ui, _runcatchfunc)
355 355
356 356 def _callcatch(ui, func):
357 357 """like scmutil.callcatch but handles more high-level exceptions about
358 358 config parsing and commands. besides, use handlecommandexception to handle
359 359 uncaught exceptions.
360 360 """
361 361 try:
362 362 return scmutil.callcatch(ui, func)
363 363 except error.AmbiguousCommand as inst:
364 364 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
365 365 (inst.args[0], " ".join(inst.args[1])))
366 366 except error.CommandError as inst:
367 367 if inst.args[0]:
368 368 ui.pager('help')
369 369 msgbytes = pycompat.bytestr(inst.args[1])
370 370 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
371 371 commands.help_(ui, inst.args[0], full=False, command=True)
372 372 else:
373 373 ui.pager('help')
374 374 ui.warn(_("hg: %s\n") % inst.args[1])
375 375 commands.help_(ui, 'shortlist')
376 376 except error.ParseError as inst:
377 377 _formatparse(ui.warn, inst)
378 378 return -1
379 379 except error.UnknownCommand as inst:
380 380 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
381 381 try:
382 382 # check if the command is in a disabled extension
383 383 # (but don't check for extensions themselves)
384 384 formatted = help.formattedhelp(ui, commands, inst.args[0],
385 385 unknowncmd=True)
386 386 ui.warn(nocmdmsg)
387 387 ui.write(formatted)
388 388 except (error.UnknownCommand, error.Abort):
389 389 suggested = False
390 390 if len(inst.args) == 2:
391 391 sim = _getsimilar(inst.args[1], inst.args[0])
392 392 if sim:
393 393 ui.warn(nocmdmsg)
394 394 _reportsimilar(ui.warn, sim)
395 395 suggested = True
396 396 if not suggested:
397 397 ui.pager('help')
398 398 ui.warn(nocmdmsg)
399 399 commands.help_(ui, 'shortlist')
400 400 except IOError:
401 401 raise
402 402 except KeyboardInterrupt:
403 403 raise
404 404 except: # probably re-raises
405 405 if not handlecommandexception(ui):
406 406 raise
407 407
408 408 return -1
409 409
410 410 def aliasargs(fn, givenargs):
411 411 args = []
412 412 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
413 413 if not util.safehasattr(fn, '_origfunc'):
414 414 args = getattr(fn, 'args', args)
415 415 if args:
416 416 cmd = ' '.join(map(procutil.shellquote, args))
417 417
418 418 nums = []
419 419 def replacer(m):
420 420 num = int(m.group(1)) - 1
421 421 nums.append(num)
422 422 if num < len(givenargs):
423 423 return givenargs[num]
424 424 raise error.Abort(_('too few arguments for command alias'))
425 425 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
426 426 givenargs = [x for i, x in enumerate(givenargs)
427 427 if i not in nums]
428 428 args = pycompat.shlexsplit(cmd)
429 429 return args + givenargs
430 430
431 431 def aliasinterpolate(name, args, cmd):
432 432 '''interpolate args into cmd for shell aliases
433 433
434 434 This also handles $0, $@ and "$@".
435 435 '''
436 436 # util.interpolate can't deal with "$@" (with quotes) because it's only
437 437 # built to match prefix + patterns.
438 438 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
439 439 replacemap['$0'] = name
440 440 replacemap['$$'] = '$'
441 441 replacemap['$@'] = ' '.join(args)
442 442 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
443 443 # parameters, separated out into words. Emulate the same behavior here by
444 444 # quoting the arguments individually. POSIX shells will then typically
445 445 # tokenize each argument into exactly one word.
446 446 replacemap['"$@"'] = ' '.join(procutil.shellquote(arg) for arg in args)
447 447 # escape '\$' for regex
448 448 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
449 449 r = re.compile(regex)
450 450 return r.sub(lambda x: replacemap[x.group()], cmd)
451 451
452 452 class cmdalias(object):
453 453 def __init__(self, ui, name, definition, cmdtable, source):
454 454 self.name = self.cmd = name
455 455 self.cmdname = ''
456 456 self.definition = definition
457 457 self.fn = None
458 458 self.givenargs = []
459 459 self.opts = []
460 460 self.help = ''
461 461 self.badalias = None
462 462 self.unknowncmd = False
463 463 self.source = source
464 464
465 465 try:
466 466 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
467 467 for alias, e in cmdtable.iteritems():
468 468 if e is entry:
469 469 self.cmd = alias
470 470 break
471 471 self.shadows = True
472 472 except error.UnknownCommand:
473 473 self.shadows = False
474 474
475 475 if not self.definition:
476 476 self.badalias = _("no definition for alias '%s'") % self.name
477 477 return
478 478
479 479 if self.definition.startswith('!'):
480 480 shdef = self.definition[1:]
481 481 self.shell = True
482 482 def fn(ui, *args):
483 483 env = {'HG_ARGS': ' '.join((self.name,) + args)}
484 484 def _checkvar(m):
485 485 if m.groups()[0] == '$':
486 486 return m.group()
487 487 elif int(m.groups()[0]) <= len(args):
488 488 return m.group()
489 489 else:
490 490 ui.debug("No argument found for substitution "
491 491 "of %i variable in alias '%s' definition.\n"
492 492 % (int(m.groups()[0]), self.name))
493 493 return ''
494 494 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
495 495 cmd = aliasinterpolate(self.name, args, cmd)
496 496 return ui.system(cmd, environ=env,
497 497 blockedtag='alias_%s' % self.name)
498 498 self.fn = fn
499 499 self._populatehelp(ui, name, shdef, self.fn)
500 500 return
501 501
502 502 try:
503 503 args = pycompat.shlexsplit(self.definition)
504 504 except ValueError as inst:
505 505 self.badalias = (_("error in definition for alias '%s': %s")
506 506 % (self.name, stringutil.forcebytestr(inst)))
507 507 return
508 508 earlyopts, args = _earlysplitopts(args)
509 509 if earlyopts:
510 510 self.badalias = (_("error in definition for alias '%s': %s may "
511 511 "only be given on the command line")
512 512 % (self.name, '/'.join(pycompat.ziplist(*earlyopts)
513 513 [0])))
514 514 return
515 515 self.cmdname = cmd = args.pop(0)
516 516 self.givenargs = args
517 517
518 518 try:
519 519 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
520 520 if len(tableentry) > 2:
521 521 self.fn, self.opts, cmdhelp = tableentry
522 522 else:
523 523 self.fn, self.opts = tableentry
524 524 cmdhelp = None
525 525
526 526 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
527 527
528 528 except error.UnknownCommand:
529 529 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
530 530 % (self.name, cmd))
531 531 self.unknowncmd = True
532 532 except error.AmbiguousCommand:
533 533 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
534 534 % (self.name, cmd))
535 535
536 536 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
537 537 # confine strings to be passed to i18n.gettext()
538 538 cfg = {}
539 539 for k in ('doc', 'help'):
540 540 v = ui.config('alias', '%s:%s' % (name, k), None)
541 541 if v is None:
542 542 continue
543 543 if not encoding.isasciistr(v):
544 544 self.badalias = (_("non-ASCII character in alias definition "
545 545 "'%s:%s'") % (name, k))
546 546 return
547 547 cfg[k] = v
548 548
549 549 self.help = cfg.get('help', defaulthelp or '')
550 550 if self.help and self.help.startswith("hg " + cmd):
551 551 # drop prefix in old-style help lines so hg shows the alias
552 552 self.help = self.help[4 + len(cmd):]
553 553
554 554 doc = cfg.get('doc', pycompat.getdoc(fn))
555 555 if doc is not None:
556 556 doc = pycompat.sysstr(doc)
557 557 self.__doc__ = doc
558 558
559 559 @property
560 560 def args(self):
561 561 args = pycompat.maplist(util.expandpath, self.givenargs)
562 562 return aliasargs(self.fn, args)
563 563
564 564 def __getattr__(self, name):
565 565 adefaults = {r'norepo': True, r'intents': set(),
566 566 r'optionalrepo': False, r'inferrepo': False}
567 567 if name not in adefaults:
568 568 raise AttributeError(name)
569 569 if self.badalias or util.safehasattr(self, 'shell'):
570 570 return adefaults[name]
571 571 return getattr(self.fn, name)
572 572
573 573 def __call__(self, ui, *args, **opts):
574 574 if self.badalias:
575 575 hint = None
576 576 if self.unknowncmd:
577 577 try:
578 578 # check if the command is in a disabled extension
579 579 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
580 580 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
581 581 except error.UnknownCommand:
582 582 pass
583 583 raise error.Abort(self.badalias, hint=hint)
584 584 if self.shadows:
585 585 ui.debug("alias '%s' shadows command '%s'\n" %
586 586 (self.name, self.cmdname))
587 587
588 588 ui.log('commandalias', "alias '%s' expands to '%s'\n",
589 589 self.name, self.definition)
590 590 if util.safehasattr(self, 'shell'):
591 591 return self.fn(ui, *args, **opts)
592 592 else:
593 593 try:
594 594 return util.checksignature(self.fn)(ui, *args, **opts)
595 595 except error.SignatureError:
596 596 args = ' '.join([self.cmdname] + self.args)
597 597 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
598 598 raise
599 599
600 600 class lazyaliasentry(object):
601 601 """like a typical command entry (func, opts, help), but is lazy"""
602 602
603 603 def __init__(self, ui, name, definition, cmdtable, source):
604 604 self.ui = ui
605 605 self.name = name
606 606 self.definition = definition
607 607 self.cmdtable = cmdtable.copy()
608 608 self.source = source
609 609
610 610 @util.propertycache
611 611 def _aliasdef(self):
612 612 return cmdalias(self.ui, self.name, self.definition, self.cmdtable,
613 613 self.source)
614 614
615 615 def __getitem__(self, n):
616 616 aliasdef = self._aliasdef
617 617 if n == 0:
618 618 return aliasdef
619 619 elif n == 1:
620 620 return aliasdef.opts
621 621 elif n == 2:
622 622 return aliasdef.help
623 623 else:
624 624 raise IndexError
625 625
626 626 def __iter__(self):
627 627 for i in range(3):
628 628 yield self[i]
629 629
630 630 def __len__(self):
631 631 return 3
632 632
633 633 def addaliases(ui, cmdtable):
634 634 # aliases are processed after extensions have been loaded, so they
635 635 # may use extension commands. Aliases can also use other alias definitions,
636 636 # but only if they have been defined prior to the current definition.
637 637 for alias, definition in ui.configitems('alias', ignoresub=True):
638 638 try:
639 639 if cmdtable[alias].definition == definition:
640 640 continue
641 641 except (KeyError, AttributeError):
642 642 # definition might not exist or it might not be a cmdalias
643 643 pass
644 644
645 645 source = ui.configsource('alias', alias)
646 646 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
647 647 cmdtable[alias] = entry
648 648
649 649 def _parse(ui, args):
650 650 options = {}
651 651 cmdoptions = {}
652 652
653 653 try:
654 654 args = fancyopts.fancyopts(args, commands.globalopts, options)
655 655 except getopt.GetoptError as inst:
656 656 raise error.CommandError(None, stringutil.forcebytestr(inst))
657 657
658 658 if args:
659 659 cmd, args = args[0], args[1:]
660 660 aliases, entry = cmdutil.findcmd(cmd, commands.table,
661 661 ui.configbool("ui", "strict"))
662 662 cmd = aliases[0]
663 663 args = aliasargs(entry[0], args)
664 664 defaults = ui.config("defaults", cmd)
665 665 if defaults:
666 666 args = pycompat.maplist(
667 667 util.expandpath, pycompat.shlexsplit(defaults)) + args
668 668 c = list(entry[1])
669 669 else:
670 670 cmd = None
671 671 c = []
672 672
673 673 # combine global options into local
674 674 for o in commands.globalopts:
675 675 c.append((o[0], o[1], options[o[1]], o[3]))
676 676
677 677 try:
678 678 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
679 679 except getopt.GetoptError as inst:
680 680 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
681 681
682 682 # separate global options back out
683 683 for o in commands.globalopts:
684 684 n = o[1]
685 685 options[n] = cmdoptions[n]
686 686 del cmdoptions[n]
687 687
688 688 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
689 689
690 690 def _parseconfig(ui, config):
691 691 """parse the --config options from the command line"""
692 692 configs = []
693 693
694 694 for cfg in config:
695 695 try:
696 696 name, value = [cfgelem.strip()
697 697 for cfgelem in cfg.split('=', 1)]
698 698 section, name = name.split('.', 1)
699 699 if not section or not name:
700 700 raise IndexError
701 701 ui.setconfig(section, name, value, '--config')
702 702 configs.append((section, name, value))
703 703 except (IndexError, ValueError):
704 704 raise error.Abort(_('malformed --config option: %r '
705 705 '(use --config section.name=value)')
706 706 % pycompat.bytestr(cfg))
707 707
708 708 return configs
709 709
710 710 def _earlyparseopts(ui, args):
711 711 options = {}
712 712 fancyopts.fancyopts(args, commands.globalopts, options,
713 713 gnu=not ui.plain('strictflags'), early=True,
714 714 optaliases={'repository': ['repo']})
715 715 return options
716 716
717 717 def _earlysplitopts(args):
718 718 """Split args into a list of possible early options and remainder args"""
719 719 shortoptions = 'R:'
720 720 # TODO: perhaps 'debugger' should be included
721 721 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
722 722 return fancyopts.earlygetopt(args, shortoptions, longoptions,
723 723 gnu=True, keepsep=True)
724 724
725 725 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
726 726 # run pre-hook, and abort if it fails
727 727 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
728 728 pats=cmdpats, opts=cmdoptions)
729 729 try:
730 730 ret = _runcommand(ui, options, cmd, d)
731 731 # run post-hook, passing command result
732 732 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
733 733 result=ret, pats=cmdpats, opts=cmdoptions)
734 734 except Exception:
735 735 # run failure hook and re-raise
736 736 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
737 737 pats=cmdpats, opts=cmdoptions)
738 738 raise
739 739 return ret
740 740
741 741 def _getlocal(ui, rpath, wd=None):
742 742 """Return (path, local ui object) for the given target path.
743 743
744 744 Takes paths in [cwd]/.hg/hgrc into account."
745 745 """
746 746 if wd is None:
747 747 try:
748 748 wd = pycompat.getcwd()
749 749 except OSError as e:
750 750 raise error.Abort(_("error getting current working directory: %s") %
751 751 encoding.strtolocal(e.strerror))
752 752 path = cmdutil.findrepo(wd) or ""
753 753 if not path:
754 754 lui = ui
755 755 else:
756 756 lui = ui.copy()
757 757 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
758 758
759 759 if rpath:
760 760 path = lui.expandpath(rpath)
761 761 lui = ui.copy()
762 762 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
763 763
764 764 return path, lui
765 765
766 766 def _checkshellalias(lui, ui, args):
767 767 """Return the function to run the shell alias, if it is required"""
768 768 options = {}
769 769
770 770 try:
771 771 args = fancyopts.fancyopts(args, commands.globalopts, options)
772 772 except getopt.GetoptError:
773 773 return
774 774
775 775 if not args:
776 776 return
777 777
778 778 cmdtable = commands.table
779 779
780 780 cmd = args[0]
781 781 try:
782 782 strict = ui.configbool("ui", "strict")
783 783 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
784 784 except (error.AmbiguousCommand, error.UnknownCommand):
785 785 return
786 786
787 787 cmd = aliases[0]
788 788 fn = entry[0]
789 789
790 790 if cmd and util.safehasattr(fn, 'shell'):
791 791 # shell alias shouldn't receive early options which are consumed by hg
792 792 _earlyopts, args = _earlysplitopts(args)
793 793 d = lambda: fn(ui, *args[1:])
794 794 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
795 795 [], {})
796 796
797 797 def _dispatch(req):
798 798 args = req.args
799 799 ui = req.ui
800 800
801 801 # check for cwd
802 802 cwd = req.earlyoptions['cwd']
803 803 if cwd:
804 804 os.chdir(cwd)
805 805
806 806 rpath = req.earlyoptions['repository']
807 807 path, lui = _getlocal(ui, rpath)
808 808
809 809 uis = {ui, lui}
810 810
811 811 if req.repo:
812 812 uis.add(req.repo.ui)
813 813
814 814 if req.earlyoptions['profile']:
815 815 for ui_ in uis:
816 816 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
817 817
818 818 profile = lui.configbool('profiling', 'enabled')
819 819 with profiling.profile(lui, enabled=profile) as profiler:
820 820 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
821 821 # reposetup
822 822 extensions.loadall(lui)
823 823 # Propagate any changes to lui.__class__ by extensions
824 824 ui.__class__ = lui.__class__
825 825
826 826 # (uisetup and extsetup are handled in extensions.loadall)
827 827
828 828 # (reposetup is handled in hg.repository)
829 829
830 830 addaliases(lui, commands.table)
831 831
832 832 # All aliases and commands are completely defined, now.
833 833 # Check abbreviation/ambiguity of shell alias.
834 834 shellaliasfn = _checkshellalias(lui, ui, args)
835 835 if shellaliasfn:
836 836 return shellaliasfn()
837 837
838 838 # check for fallback encoding
839 839 fallback = lui.config('ui', 'fallbackencoding')
840 840 if fallback:
841 841 encoding.fallbackencoding = fallback
842 842
843 843 fullargs = args
844 844 cmd, func, args, options, cmdoptions = _parse(lui, args)
845 845
846 846 if options["config"] != req.earlyoptions["config"]:
847 847 raise error.Abort(_("option --config may not be abbreviated!"))
848 848 if options["cwd"] != req.earlyoptions["cwd"]:
849 849 raise error.Abort(_("option --cwd may not be abbreviated!"))
850 850 if options["repository"] != req.earlyoptions["repository"]:
851 851 raise error.Abort(_(
852 852 "option -R has to be separated from other options (e.g. not "
853 853 "-qR) and --repository may only be abbreviated as --repo!"))
854 854 if options["debugger"] != req.earlyoptions["debugger"]:
855 855 raise error.Abort(_("option --debugger may not be abbreviated!"))
856 856 # don't validate --profile/--traceback, which can be enabled from now
857 857
858 858 if options["encoding"]:
859 859 encoding.encoding = options["encoding"]
860 860 if options["encodingmode"]:
861 861 encoding.encodingmode = options["encodingmode"]
862 862 if options["time"]:
863 863 def get_times():
864 864 t = os.times()
865 865 if t[4] == 0.0:
866 866 # Windows leaves this as zero, so use time.clock()
867 867 t = (t[0], t[1], t[2], t[3], time.clock())
868 868 return t
869 869 s = get_times()
870 870 def print_time():
871 871 t = get_times()
872 872 ui.warn(
873 873 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
874 874 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
875 875 ui.atexit(print_time)
876 876 if options["profile"]:
877 877 profiler.start()
878 878
879 879 if options['verbose'] or options['debug'] or options['quiet']:
880 880 for opt in ('verbose', 'debug', 'quiet'):
881 881 val = pycompat.bytestr(bool(options[opt]))
882 882 for ui_ in uis:
883 883 ui_.setconfig('ui', opt, val, '--' + opt)
884 884
885 885 if options['traceback']:
886 886 for ui_ in uis:
887 887 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
888 888
889 889 if options['noninteractive']:
890 890 for ui_ in uis:
891 891 ui_.setconfig('ui', 'interactive', 'off', '-y')
892 892
893 893 if cmdoptions.get('insecure', False):
894 894 for ui_ in uis:
895 895 ui_.insecureconnections = True
896 896
897 897 # setup color handling before pager, because setting up pager
898 898 # might cause incorrect console information
899 899 coloropt = options['color']
900 900 for ui_ in uis:
901 901 if coloropt:
902 902 ui_.setconfig('ui', 'color', coloropt, '--color')
903 903 color.setup(ui_)
904 904
905 905 if stringutil.parsebool(options['pager']):
906 906 # ui.pager() expects 'internal-always-' prefix in this case
907 907 ui.pager('internal-always-' + cmd)
908 908 elif options['pager'] != 'auto':
909 909 for ui_ in uis:
910 910 ui_.disablepager()
911 911
912 912 if options['version']:
913 913 return commands.version_(ui)
914 914 if options['help']:
915 915 return commands.help_(ui, cmd, command=cmd is not None)
916 916 elif not cmd:
917 917 return commands.help_(ui, 'shortlist')
918 918
919 919 repo = None
920 920 cmdpats = args[:]
921 921 if not func.norepo:
922 922 # use the repo from the request only if we don't have -R
923 923 if not rpath and not cwd:
924 924 repo = req.repo
925 925
926 926 if repo:
927 927 # set the descriptors of the repo ui to those of ui
928 928 repo.ui.fin = ui.fin
929 929 repo.ui.fout = ui.fout
930 930 repo.ui.ferr = ui.ferr
931 931 else:
932 932 try:
933 933 repo = hg.repository(ui, path=path,
934 934 presetupfuncs=req.prereposetups,
935 935 intents=func.intents)
936 936 if not repo.local():
937 937 raise error.Abort(_("repository '%s' is not local")
938 938 % path)
939 939 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
940 940 'repo')
941 941 except error.RequirementError:
942 942 raise
943 943 except error.RepoError:
944 944 if rpath: # invalid -R path
945 945 raise
946 946 if not func.optionalrepo:
947 947 if func.inferrepo and args and not path:
948 948 # try to infer -R from command args
949 949 repos = pycompat.maplist(cmdutil.findrepo, args)
950 950 guess = repos[0]
951 951 if guess and repos.count(guess) == len(repos):
952 952 req.args = ['--repository', guess] + fullargs
953 953 req.earlyoptions['repository'] = guess
954 954 return _dispatch(req)
955 955 if not path:
956 956 raise error.RepoError(_("no repository found in"
957 957 " '%s' (.hg not found)")
958 958 % pycompat.getcwd())
959 959 raise
960 960 if repo:
961 961 ui = repo.ui
962 962 if options['hidden']:
963 963 repo = repo.unfiltered()
964 964 args.insert(0, repo)
965 965 elif rpath:
966 966 ui.warn(_("warning: --repository ignored\n"))
967 967
968 968 msg = _formatargs(fullargs)
969 969 ui.log("command", '%s\n', msg)
970 970 strcmdopt = pycompat.strkwargs(cmdoptions)
971 971 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
972 972 try:
973 973 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
974 974 cmdpats, cmdoptions)
975 975 finally:
976 976 if repo and repo != req.repo:
977 977 repo.close()
978 978
979 979 def _runcommand(ui, options, cmd, cmdfunc):
980 980 """Run a command function, possibly with profiling enabled."""
981 981 try:
982 982 return cmdfunc()
983 983 except error.SignatureError:
984 984 raise error.CommandError(cmd, _('invalid arguments'))
985 985
986 986 def _exceptionwarning(ui):
987 987 """Produce a warning message for the current active exception"""
988 988
989 989 # For compatibility checking, we discard the portion of the hg
990 990 # version after the + on the assumption that if a "normal
991 991 # user" is running a build with a + in it the packager
992 992 # probably built from fairly close to a tag and anyone with a
993 993 # 'make local' copy of hg (where the version number can be out
994 994 # of date) will be clueful enough to notice the implausible
995 995 # version number and try updating.
996 996 ct = util.versiontuple(n=2)
997 997 worst = None, ct, ''
998 998 if ui.config('ui', 'supportcontact') is None:
999 999 for name, mod in extensions.extensions():
1000 1000 # 'testedwith' should be bytes, but not all extensions are ported
1001 1001 # to py3 and we don't want UnicodeException because of that.
1002 1002 testedwith = stringutil.forcebytestr(getattr(mod, 'testedwith', ''))
1003 1003 report = getattr(mod, 'buglink', _('the extension author.'))
1004 1004 if not testedwith.strip():
1005 1005 # We found an untested extension. It's likely the culprit.
1006 1006 worst = name, 'unknown', report
1007 1007 break
1008 1008
1009 1009 # Never blame on extensions bundled with Mercurial.
1010 1010 if extensions.ismoduleinternal(mod):
1011 1011 continue
1012 1012
1013 1013 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1014 1014 if ct in tested:
1015 1015 continue
1016 1016
1017 1017 lower = [t for t in tested if t < ct]
1018 1018 nearest = max(lower or tested)
1019 1019 if worst[0] is None or nearest < worst[1]:
1020 1020 worst = name, nearest, report
1021 1021 if worst[0] is not None:
1022 1022 name, testedwith, report = worst
1023 1023 if not isinstance(testedwith, (bytes, str)):
1024 1024 testedwith = '.'.join([stringutil.forcebytestr(c)
1025 1025 for c in testedwith])
1026 1026 warning = (_('** Unknown exception encountered with '
1027 1027 'possibly-broken third-party extension %s\n'
1028 1028 '** which supports versions %s of Mercurial.\n'
1029 1029 '** Please disable %s and try your action again.\n'
1030 1030 '** If that fixes the bug please report it to %s\n')
1031 1031 % (name, testedwith, name, report))
1032 1032 else:
1033 1033 bugtracker = ui.config('ui', 'supportcontact')
1034 1034 if bugtracker is None:
1035 1035 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1036 1036 warning = (_("** unknown exception encountered, "
1037 1037 "please report by visiting\n** ") + bugtracker + '\n')
1038 1038 sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
1039 1039 warning += ((_("** Python %s\n") % sysversion) +
1040 1040 (_("** Mercurial Distributed SCM (version %s)\n") %
1041 1041 util.version()) +
1042 1042 (_("** Extensions loaded: %s\n") %
1043 1043 ", ".join([x[0] for x in extensions.extensions()])))
1044 1044 return warning
1045 1045
1046 1046 def handlecommandexception(ui):
1047 1047 """Produce a warning message for broken commands
1048 1048
1049 1049 Called when handling an exception; the exception is reraised if
1050 1050 this function returns False, ignored otherwise.
1051 1051 """
1052 1052 warning = _exceptionwarning(ui)
1053 1053 ui.log("commandexception", "%s\n%s\n", warning,
1054 1054 pycompat.sysbytes(traceback.format_exc()))
1055 1055 ui.warn(warning)
1056 1056 return False # re-raise the exception
@@ -1,406 +1,417 b''
1 1 setup
2 2
3 3 $ cat > myextension.py <<EOF
4 4 > from mercurial import error, registrar
5 5 > cmdtable = {}
6 6 > command = registrar.command(cmdtable)
7 7 > @command(b'crash', [], b'hg crash')
8 8 > def crash(ui, *args, **kwargs):
9 9 > raise Exception("oops")
10 > @command(b'abort', [], b'hg abort')
11 > def abort(ui, *args, **kwargs):
12 > raise error.Abort(b"oops")
10 13 > EOF
11 14 $ abspath=`pwd`/myextension.py
12 15
13 16 $ cat >> $HGRCPATH <<EOF
14 17 > [extensions]
15 18 > blackbox=
16 19 > mock=$TESTDIR/mockblackbox.py
17 20 > mq=
18 21 > myextension=$TESTTMP/myextension.py
19 22 > [alias]
20 23 > confuse = log --limit 3
21 24 > so-confusing = confuse --style compact
22 25 > EOF
23 26
24 27 $ hg init blackboxtest
25 28 $ cd blackboxtest
26 29
27 30 command, exit codes, and duration
28 31
29 32 $ echo a > a
30 33 $ hg add a
31 34 $ hg blackbox --config blackbox.dirty=True
32 35 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob)
33 36 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
34 37 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
35 38 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* (glob)
36 39
37 40 failure exit code
38 41 $ rm ./.hg/blackbox.log
39 42 $ hg add non-existent
40 43 non-existent: $ENOENT$
41 44 [1]
42 45 $ hg blackbox
43 46 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add non-existent
44 47 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add non-existent exited 1 after * seconds (glob)
45 48 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
46 49
50 abort exit code
51 $ rm ./.hg/blackbox.log
52 $ hg abort 2> /dev/null
53 [255]
54 $ hg blackbox -l 2
55 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> abort exited 255 after * seconds (glob)
56 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox -l 2
57
47 58 unhandled exception
48 59 $ rm ./.hg/blackbox.log
49 60 $ hg crash 2> /dev/null
50 61 [1]
51 62 $ hg blackbox -l 2
52 63 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> crash exited 1 after * seconds (glob)
53 64 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox -l 2
54 65
55 66 alias expansion is logged
56 67 $ rm ./.hg/blackbox.log
57 68 $ hg confuse
58 69 $ hg blackbox
59 70 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse
60 71 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
61 72 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob)
62 73 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
63 74
64 75 recursive aliases work correctly
65 76 $ rm ./.hg/blackbox.log
66 77 $ hg so-confusing
67 78 $ hg blackbox
68 79 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing
69 80 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'so-confusing' expands to 'confuse --style compact'
70 81 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
71 82 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing exited 0 after * seconds (glob)
72 83 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
73 84
74 85 incoming change tracking
75 86
76 87 create two heads to verify that we only see one change in the log later
77 88 $ hg commit -ma
78 89 $ hg up null
79 90 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
80 91 $ echo b > b
81 92 $ hg commit -Amb
82 93 adding b
83 94 created new head
84 95
85 96 clone, commit, pull
86 97 $ hg clone . ../blackboxtest2
87 98 updating to branch default
88 99 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 100 $ echo c > c
90 101 $ hg commit -Amc
91 102 adding c
92 103 $ cd ../blackboxtest2
93 104 $ hg pull
94 105 pulling from $TESTTMP/blackboxtest
95 106 searching for changes
96 107 adding changesets
97 108 adding manifests
98 109 adding file changes
99 110 added 1 changesets with 1 changes to 1 files
100 111 new changesets d02f48003e62
101 112 (run 'hg update' to get a working copy)
102 113 $ hg blackbox -l 6
103 114 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull
104 115 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated served branch cache in * seconds (glob)
105 116 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote served branch cache with 1 labels and 2 nodes
106 117 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> 1 incoming changes - new heads: d02f48003e62
107 118 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull exited 0 after * seconds (glob)
108 119 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
109 120
110 121 we must not cause a failure if we cannot write to the log
111 122
112 123 $ hg rollback
113 124 repository tip rolled back to revision 1 (undo pull)
114 125
115 126 $ mv .hg/blackbox.log .hg/blackbox.log-
116 127 $ mkdir .hg/blackbox.log
117 128 $ hg --debug incoming
118 129 warning: cannot write to blackbox.log: * (glob)
119 130 comparing with $TESTTMP/blackboxtest
120 131 query 1; heads
121 132 searching for changes
122 133 all local heads known remotely
123 134 changeset: 2:d02f48003e62c24e2659d97d30f2a83abe5d5d51
124 135 tag: tip
125 136 phase: draft
126 137 parent: 1:6563da9dcf87b1949716e38ff3e3dfaa3198eb06
127 138 parent: -1:0000000000000000000000000000000000000000
128 139 manifest: 2:ab9d46b053ebf45b7996f2922b9893ff4b63d892
129 140 user: test
130 141 date: Thu Jan 01 00:00:00 1970 +0000
131 142 files+: c
132 143 extra: branch=default
133 144 description:
134 145 c
135 146
136 147
137 148 $ hg pull
138 149 pulling from $TESTTMP/blackboxtest
139 150 searching for changes
140 151 adding changesets
141 152 adding manifests
142 153 adding file changes
143 154 added 1 changesets with 1 changes to 1 files
144 155 new changesets d02f48003e62
145 156 (run 'hg update' to get a working copy)
146 157
147 158 a failure reading from the log is fatal
148 159
149 160 $ hg blackbox -l 3
150 161 abort: *$TESTTMP/blackboxtest2/.hg/blackbox.log* (glob)
151 162 [255]
152 163
153 164 $ rmdir .hg/blackbox.log
154 165 $ mv .hg/blackbox.log- .hg/blackbox.log
155 166
156 167 backup bundles get logged
157 168
158 169 $ touch d
159 170 $ hg commit -Amd
160 171 adding d
161 172 created new head
162 173 $ hg strip tip
163 174 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
164 175 saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
165 176 $ hg blackbox -l 6
166 177 1970/01/01 00:00:00 bob @73f6ee326b27d820b0472f1a825e3a50f3dc489b (5000)> strip tip
167 178 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/73f6ee326b27-7612e004-backup.hg
168 179 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated base branch cache in * seconds (glob)
169 180 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote base branch cache with 1 labels and 2 nodes
170 181 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> strip tip exited 0 after * seconds (glob)
171 182 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
172 183
173 184 extension and python hooks - use the eol extension for a pythonhook
174 185
175 186 $ echo '[extensions]' >> .hg/hgrc
176 187 $ echo 'eol=' >> .hg/hgrc
177 188 $ echo '[hooks]' >> .hg/hgrc
178 189 $ echo 'update = echo hooked' >> .hg/hgrc
179 190 $ hg update
180 191 The fsmonitor extension is incompatible with the eol extension and has been disabled. (fsmonitor !)
181 192 hooked
182 193 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
183 194 updated to "d02f48003e62: c"
184 195 1 other heads for branch "default"
185 196 $ cat >> .hg/hgrc <<EOF
186 197 > [extensions]
187 198 > # disable eol, because it is not needed for subsequent tests
188 199 > # (in addition, keeping it requires extra care for fsmonitor)
189 200 > eol=!
190 201 > EOF
191 202 $ hg blackbox -l 5
192 203 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update (no-chg !)
193 204 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
194 205 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob)
195 206 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob)
196 207 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> serve --cmdserver chgunix --address $TESTTMP.chgsock/server.* --daemon-postexec 'chdir:/' (glob) (chg !)
197 208 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 5
198 209
199 210 log rotation
200 211
201 212 $ echo '[blackbox]' >> .hg/hgrc
202 213 $ echo 'maxsize = 20 b' >> .hg/hgrc
203 214 $ echo 'maxfiles = 3' >> .hg/hgrc
204 215 $ hg status
205 216 $ hg status
206 217 $ hg status
207 218 $ hg tip -q
208 219 2:d02f48003e62
209 220 $ ls .hg/blackbox.log*
210 221 .hg/blackbox.log
211 222 .hg/blackbox.log.1
212 223 .hg/blackbox.log.2
213 224 $ cd ..
214 225
215 226 $ hg init blackboxtest3
216 227 $ cd blackboxtest3
217 228 $ hg blackbox
218 229 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest3 exited 0 after * seconds (glob)
219 230 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
220 231 $ mv .hg/blackbox.log .hg/blackbox.log-
221 232 $ mkdir .hg/blackbox.log
222 233 $ sed -e 's/\(.*test1.*\)/#\1/; s#\(.*commit2.*\)#os.rmdir(".hg/blackbox.log")\
223 234 > os.rename(".hg/blackbox.log-", ".hg/blackbox.log")\
224 235 > \1#' $TESTDIR/test-dispatch.py > ../test-dispatch.py
225 236 $ $PYTHON $TESTDIR/blackbox-readonly-dispatch.py
226 237 running: --debug add foo
227 238 warning: cannot write to blackbox.log: Is a directory (no-windows !)
228 239 warning: cannot write to blackbox.log: $TESTTMP/blackboxtest3/.hg/blackbox.log: Access is denied (windows !)
229 240 adding foo
230 241 result: 0
231 242 running: --debug commit -m commit1 -d 2000-01-01 foo
232 243 warning: cannot write to blackbox.log: Is a directory (no-windows !)
233 244 warning: cannot write to blackbox.log: $TESTTMP/blackboxtest3/.hg/blackbox.log: Access is denied (windows !)
234 245 committing files:
235 246 foo
236 247 committing manifest
237 248 committing changelog
238 249 updating the branch cache
239 250 committed changeset 0:0e46349438790c460c5c9f7546bfcd39b267bbd2
240 251 result: 0
241 252 running: --debug commit -m commit2 -d 2000-01-02 foo
242 253 committing files:
243 254 foo
244 255 committing manifest
245 256 committing changelog
246 257 updating the branch cache
247 258 committed changeset 1:45589e459b2edfbf3dbde7e01f611d2c1e7453d7
248 259 result: 0
249 260 running: --debug log -r 0
250 261 changeset: 0:0e46349438790c460c5c9f7546bfcd39b267bbd2
251 262 phase: draft
252 263 parent: -1:0000000000000000000000000000000000000000
253 264 parent: -1:0000000000000000000000000000000000000000
254 265 manifest: 0:9091aa5df980aea60860a2e39c95182e68d1ddec
255 266 user: test
256 267 date: Sat Jan 01 00:00:00 2000 +0000
257 268 files+: foo
258 269 extra: branch=default
259 270 description:
260 271 commit1
261 272
262 273
263 274 result: 0
264 275 running: --debug log -r tip
265 276 changeset: 1:45589e459b2edfbf3dbde7e01f611d2c1e7453d7
266 277 tag: tip
267 278 phase: draft
268 279 parent: 0:0e46349438790c460c5c9f7546bfcd39b267bbd2
269 280 parent: -1:0000000000000000000000000000000000000000
270 281 manifest: 1:895aa9b7886f89dd017a6d62524e1f9180b04df9
271 282 user: test
272 283 date: Sun Jan 02 00:00:00 2000 +0000
273 284 files: foo
274 285 extra: branch=default
275 286 description:
276 287 commit2
277 288
278 289
279 290 result: 0
280 291 $ hg blackbox
281 292 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updating the branch cache
282 293 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updated served branch cache in * seconds (glob)
283 294 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> wrote served branch cache with 1 labels and 1 nodes
284 295 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug commit -m commit2 -d 2000-01-02 foo exited 0 after *.?? seconds (glob)
285 296 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r 0
286 297 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> writing .hg/cache/tags2-visible with 0 tags
287 298 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r 0 exited 0 after *.?? seconds (glob)
288 299 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r tip
289 300 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r tip exited 0 after *.?? seconds (glob)
290 301 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> blackbox
291 302
292 303 Test log recursion from dirty status check
293 304
294 305 $ cat > ../r.py <<EOF
295 306 > from mercurial import context, error, extensions
296 307 > x=[False]
297 308 > def status(orig, *args, **opts):
298 309 > args[0].repo().ui.log(b"broken", b"recursion?")
299 310 > return orig(*args, **opts)
300 311 > def reposetup(ui, repo):
301 312 > extensions.wrapfunction(context.basectx, 'status', status)
302 313 > EOF
303 314 $ hg id --config extensions.x=../r.py --config blackbox.dirty=True
304 315 45589e459b2e tip
305 316
306 317 cleanup
307 318 $ cd ..
308 319
309 320 #if chg
310 321
311 322 when using chg, blackbox.log should get rotated correctly
312 323
313 324 $ cat > $TESTTMP/noop.py << EOF
314 325 > from __future__ import absolute_import
315 326 > import time
316 327 > from mercurial import registrar, scmutil
317 328 > cmdtable = {}
318 329 > command = registrar.command(cmdtable)
319 330 > @command('noop')
320 331 > def noop(ui, repo):
321 332 > pass
322 333 > EOF
323 334
324 335 $ hg init blackbox-chg
325 336 $ cd blackbox-chg
326 337
327 338 $ cat > .hg/hgrc << EOF
328 339 > [blackbox]
329 340 > maxsize = 500B
330 341 > [extensions]
331 342 > # extension change forces chg to restart
332 343 > noop=$TESTTMP/noop.py
333 344 > EOF
334 345
335 346 $ $PYTHON -c 'print("a" * 400)' > .hg/blackbox.log
336 347 $ chg noop
337 348 $ chg noop
338 349 $ chg noop
339 350 $ chg noop
340 351 $ chg noop
341 352
342 353 $ cat > showsize.py << 'EOF'
343 354 > import os, sys
344 355 > limit = 500
345 356 > for p in sys.argv[1:]:
346 357 > size = os.stat(p).st_size
347 358 > if size >= limit:
348 359 > desc = '>='
349 360 > else:
350 361 > desc = '<'
351 362 > print('%s: %s %d' % (p, desc, limit))
352 363 > EOF
353 364
354 365 $ $PYTHON showsize.py .hg/blackbox*
355 366 .hg/blackbox.log: < 500
356 367 .hg/blackbox.log.1: >= 500
357 368 .hg/blackbox.log.2: >= 500
358 369
359 370 $ cd ..
360 371
361 372 With chg, blackbox should not create the log file if the repo is gone
362 373
363 374 $ hg init repo1
364 375 $ hg --config extensions.a=! -R repo1 log
365 376 $ rm -rf $TESTTMP/repo1
366 377 $ hg --config extensions.a=! init repo1
367 378
368 379 #endif
369 380
370 381 blackbox should work if repo.ui.log is not called (issue5518)
371 382
372 383 $ cat > $TESTTMP/raise.py << EOF
373 384 > from __future__ import absolute_import
374 385 > from mercurial import registrar, scmutil
375 386 > cmdtable = {}
376 387 > command = registrar.command(cmdtable)
377 388 > @command(b'raise')
378 389 > def raisecmd(*args):
379 390 > raise RuntimeError('raise')
380 391 > EOF
381 392
382 393 $ cat >> $HGRCPATH << EOF
383 394 > [blackbox]
384 395 > track = commandexception
385 396 > [extensions]
386 397 > raise=$TESTTMP/raise.py
387 398 > EOF
388 399
389 400 $ hg init $TESTTMP/blackbox-exception-only
390 401 $ cd $TESTTMP/blackbox-exception-only
391 402
392 403 #if chg
393 404 (chg exits 255 because it fails to receive an exit code)
394 405 $ hg raise 2>/dev/null
395 406 [255]
396 407 #else
397 408 (hg exits 1 because Python default exit code for uncaught exception is 1)
398 409 $ hg raise 2>/dev/null
399 410 [1]
400 411 #endif
401 412
402 413 $ head -1 .hg/blackbox.log
403 414 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> ** Unknown exception encountered with possibly-broken third-party extension mock
404 415 $ tail -2 .hg/blackbox.log
405 416 RuntimeError: raise
406 417
General Comments 0
You need to be logged in to leave comments. Login now