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