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