]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/hg/mercurial/dispatch.py
/sys/lib/dist/mkfile: test for .git directory
[plan9front.git] / sys / src / cmd / hg / mercurial / dispatch.py
1 # dispatch.py - command dispatching for mercurial
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
7
8 from i18n import _
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
10 import util, commands, hg, fancyopts, extensions, hook, error
11 import cmdutil, encoding
12 import ui as _ui
13
14 def run():
15     "run the command in sys.argv"
16     sys.exit(dispatch(sys.argv[1:]))
17
18 def dispatch(args):
19     "run the command specified in args"
20     try:
21         u = _ui.ui()
22         if '--traceback' in args:
23             u.setconfig('ui', 'traceback', 'on')
24     except util.Abort, inst:
25         sys.stderr.write(_("abort: %s\n") % inst)
26         return -1
27     return _runcatch(u, args)
28
29 def _runcatch(ui, args):
30     def catchterm(*args):
31         raise error.SignalInterrupt
32
33     for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
34         num = getattr(signal, name, None)
35         if num: signal.signal(num, catchterm)
36
37     try:
38         try:
39             # enter the debugger before command execution
40             if '--debugger' in args:
41                 pdb.set_trace()
42             try:
43                 return _dispatch(ui, args)
44             finally:
45                 ui.flush()
46         except:
47             # enter the debugger when we hit an exception
48             if '--debugger' in args:
49                 pdb.post_mortem(sys.exc_info()[2])
50             ui.traceback()
51             raise
52
53     # Global exception handling, alphabetically
54     # Mercurial-specific first, followed by built-in and library exceptions
55     except error.AmbiguousCommand, inst:
56         ui.warn(_("hg: command '%s' is ambiguous:\n    %s\n") %
57                 (inst.args[0], " ".join(inst.args[1])))
58     except error.ConfigError, inst:
59         ui.warn(_("hg: %s\n") % inst.args[0])
60     except error.LockHeld, inst:
61         if inst.errno == errno.ETIMEDOUT:
62             reason = _('timed out waiting for lock held by %s') % inst.locker
63         else:
64             reason = _('lock held by %s') % inst.locker
65         ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
66     except error.LockUnavailable, inst:
67         ui.warn(_("abort: could not lock %s: %s\n") %
68                (inst.desc or inst.filename, inst.strerror))
69     except error.ParseError, inst:
70         if inst.args[0]:
71             ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
72             commands.help_(ui, inst.args[0])
73         else:
74             ui.warn(_("hg: %s\n") % inst.args[1])
75             commands.help_(ui, 'shortlist')
76     except error.RepoError, inst:
77         ui.warn(_("abort: %s!\n") % inst)
78     except error.ResponseError, inst:
79         ui.warn(_("abort: %s") % inst.args[0])
80         if not isinstance(inst.args[1], basestring):
81             ui.warn(" %r\n" % (inst.args[1],))
82         elif not inst.args[1]:
83             ui.warn(_(" empty string\n"))
84         else:
85             ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
86     except error.RevlogError, inst:
87         ui.warn(_("abort: %s!\n") % inst)
88     except error.SignalInterrupt:
89         ui.warn(_("killed!\n"))
90     except error.UnknownCommand, inst:
91         ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
92         commands.help_(ui, 'shortlist')
93     except util.Abort, inst:
94         ui.warn(_("abort: %s\n") % inst)
95     except ImportError, inst:
96         m = str(inst).split()[-1]
97         ui.warn(_("abort: could not import module %s!\n") % m)
98         if m in "mpatch bdiff".split():
99             ui.warn(_("(did you forget to compile extensions?)\n"))
100         elif m in "zlib".split():
101             ui.warn(_("(is your Python install correct?)\n"))
102     except IOError, inst:
103         if hasattr(inst, "code"):
104             ui.warn(_("abort: %s\n") % inst)
105         elif hasattr(inst, "reason"):
106             try: # usually it is in the form (errno, strerror)
107                 reason = inst.reason.args[1]
108             except: # it might be anything, for example a string
109                 reason = inst.reason
110             ui.warn(_("abort: error: %s\n") % reason)
111         elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
112             if ui.debugflag:
113                 ui.warn(_("broken pipe\n"))
114         elif getattr(inst, "strerror", None):
115             if getattr(inst, "filename", None):
116                 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
117             else:
118                 ui.warn(_("abort: %s\n") % inst.strerror)
119         else:
120             raise
121     except OSError, inst:
122         if getattr(inst, "filename", None):
123             ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
124         else:
125             ui.warn(_("abort: %s\n") % inst.strerror)
126     except KeyboardInterrupt:
127         try:
128             ui.warn(_("interrupted!\n"))
129         except IOError, inst:
130             if inst.errno == errno.EPIPE:
131                 if ui.debugflag:
132                     ui.warn(_("\nbroken pipe\n"))
133             else:
134                 raise
135     except MemoryError:
136         ui.warn(_("abort: out of memory\n"))
137     except SystemExit, inst:
138         # Commands shouldn't sys.exit directly, but give a return code.
139         # Just in case catch this and and pass exit code to caller.
140         return inst.code
141     except socket.error, inst:
142         ui.warn(_("abort: %s\n") % inst.args[-1])
143     except:
144         ui.warn(_("** unknown exception encountered, details follow\n"))
145         ui.warn(_("** report bug details to "
146                  "http://mercurial.selenic.com/bts/\n"))
147         ui.warn(_("** or mercurial@selenic.com\n"))
148         ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
149                % util.version())
150         ui.warn(_("** Extensions loaded: %s\n")
151                % ", ".join([x[0] for x in extensions.extensions()]))
152         raise
153
154     return -1
155
156 def _findrepo(p):
157     while not os.path.isdir(os.path.join(p, ".hg")):
158         oldp, p = p, os.path.dirname(p)
159         if p == oldp:
160             return None
161
162     return p
163
164 def aliasargs(fn):
165     if hasattr(fn, 'args'):
166         return fn.args
167     return []
168
169 class cmdalias(object):
170     def __init__(self, name, definition, cmdtable):
171         self.name = name
172         self.definition = definition
173         self.args = []
174         self.opts = []
175         self.help = ''
176         self.norepo = True
177
178         try:
179             cmdutil.findcmd(self.name, cmdtable, True)
180             self.shadows = True
181         except error.UnknownCommand:
182             self.shadows = False
183
184         if not self.definition:
185             def fn(ui, *args):
186                 ui.warn(_("no definition for alias '%s'\n") % self.name)
187                 return 1
188             self.fn = fn
189
190             return
191
192         args = shlex.split(self.definition)
193         cmd = args.pop(0)
194         opts = []
195         help = ''
196
197         try:
198             self.fn, self.opts, self.help = cmdutil.findcmd(cmd, cmdtable, False)[1]
199             self.args = aliasargs(self.fn) + args
200             if cmd not in commands.norepo.split(' '):
201                 self.norepo = False
202         except error.UnknownCommand:
203             def fn(ui, *args):
204                 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
205                             % (self.name, cmd))
206                 return 1
207             self.fn = fn
208         except error.AmbiguousCommand:
209             def fn(ui, *args):
210                 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
211                             % (self.name, cmd))
212                 return 1
213             self.fn = fn
214
215     def __call__(self, ui, *args, **opts):
216         if self.shadows:
217             ui.debug(_("alias '%s' shadows command\n") % self.name)
218
219         return self.fn(ui, *args, **opts)
220
221 def addaliases(ui, cmdtable):
222     # aliases are processed after extensions have been loaded, so they
223     # may use extension commands. Aliases can also use other alias definitions,
224     # but only if they have been defined prior to the current definition.
225     for alias, definition in ui.configitems('alias'):
226         aliasdef = cmdalias(alias, definition, cmdtable)
227         cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
228         if aliasdef.norepo:
229             commands.norepo += ' %s' % alias
230
231 def _parse(ui, args):
232     options = {}
233     cmdoptions = {}
234
235     try:
236         args = fancyopts.fancyopts(args, commands.globalopts, options)
237     except fancyopts.getopt.GetoptError, inst:
238         raise error.ParseError(None, inst)
239
240     if args:
241         cmd, args = args[0], args[1:]
242         aliases, i = cmdutil.findcmd(cmd, commands.table,
243                                      ui.config("ui", "strict"))
244         cmd = aliases[0]
245         args = aliasargs(i[0]) + args
246         defaults = ui.config("defaults", cmd)
247         if defaults:
248             args = shlex.split(defaults) + args
249         c = list(i[1])
250     else:
251         cmd = None
252         c = []
253
254     # combine global options into local
255     for o in commands.globalopts:
256         c.append((o[0], o[1], options[o[1]], o[3]))
257
258     try:
259         args = fancyopts.fancyopts(args, c, cmdoptions, True)
260     except fancyopts.getopt.GetoptError, inst:
261         raise error.ParseError(cmd, inst)
262
263     # separate global options back out
264     for o in commands.globalopts:
265         n = o[1]
266         options[n] = cmdoptions[n]
267         del cmdoptions[n]
268
269     return (cmd, cmd and i[0] or None, args, options, cmdoptions)
270
271 def _parseconfig(ui, config):
272     """parse the --config options from the command line"""
273     for cfg in config:
274         try:
275             name, value = cfg.split('=', 1)
276             section, name = name.split('.', 1)
277             if not section or not name:
278                 raise IndexError
279             ui.setconfig(section, name, value)
280         except (IndexError, ValueError):
281             raise util.Abort(_('malformed --config option: %s') % cfg)
282
283 def _earlygetopt(aliases, args):
284     """Return list of values for an option (or aliases).
285
286     The values are listed in the order they appear in args.
287     The options and values are removed from args.
288     """
289     try:
290         argcount = args.index("--")
291     except ValueError:
292         argcount = len(args)
293     shortopts = [opt for opt in aliases if len(opt) == 2]
294     values = []
295     pos = 0
296     while pos < argcount:
297         if args[pos] in aliases:
298             if pos + 1 >= argcount:
299                 # ignore and let getopt report an error if there is no value
300                 break
301             del args[pos]
302             values.append(args.pop(pos))
303             argcount -= 2
304         elif args[pos][:2] in shortopts:
305             # short option can have no following space, e.g. hg log -Rfoo
306             values.append(args.pop(pos)[2:])
307             argcount -= 1
308         else:
309             pos += 1
310     return values
311
312 def runcommand(lui, repo, cmd, fullargs, ui, options, d):
313     # run pre-hook, and abort if it fails
314     ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
315     if ret:
316         return ret
317     ret = _runcommand(ui, options, cmd, d)
318     # run post-hook, passing command result
319     hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
320               result = ret)
321     return ret
322
323 _loaded = set()
324 def _dispatch(ui, args):
325     # read --config before doing anything else
326     # (e.g. to change trust settings for reading .hg/hgrc)
327     _parseconfig(ui, _earlygetopt(['--config'], args))
328
329     # check for cwd
330     cwd = _earlygetopt(['--cwd'], args)
331     if cwd:
332         os.chdir(cwd[-1])
333
334     # read the local repository .hgrc into a local ui object
335     path = _findrepo(os.getcwd()) or ""
336     if not path:
337         lui = ui
338     if path:
339         try:
340             lui = ui.copy()
341             lui.readconfig(os.path.join(path, ".hg", "hgrc"))
342         except IOError:
343             pass
344
345     # now we can expand paths, even ones in .hg/hgrc
346     rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
347     if rpath:
348         path = lui.expandpath(rpath[-1])
349         lui = ui.copy()
350         lui.readconfig(os.path.join(path, ".hg", "hgrc"))
351
352     extensions.loadall(lui)
353     for name, module in extensions.extensions():
354         if name in _loaded:
355             continue
356
357         # setup extensions
358         # TODO this should be generalized to scheme, where extensions can
359         #      redepend on other extensions.  then we should toposort them, and
360         #      do initialization in correct order
361         extsetup = getattr(module, 'extsetup', None)
362         if extsetup:
363             extsetup()
364
365         cmdtable = getattr(module, 'cmdtable', {})
366         overrides = [cmd for cmd in cmdtable if cmd in commands.table]
367         if overrides:
368             ui.warn(_("extension '%s' overrides commands: %s\n")
369                     % (name, " ".join(overrides)))
370         commands.table.update(cmdtable)
371         _loaded.add(name)
372
373     addaliases(lui, commands.table)
374
375     # check for fallback encoding
376     fallback = lui.config('ui', 'fallbackencoding')
377     if fallback:
378         encoding.fallbackencoding = fallback
379
380     fullargs = args
381     cmd, func, args, options, cmdoptions = _parse(lui, args)
382
383     if options["config"]:
384         raise util.Abort(_("Option --config may not be abbreviated!"))
385     if options["cwd"]:
386         raise util.Abort(_("Option --cwd may not be abbreviated!"))
387     if options["repository"]:
388         raise util.Abort(_(
389             "Option -R has to be separated from other options (e.g. not -qR) "
390             "and --repository may only be abbreviated as --repo!"))
391
392     if options["encoding"]:
393         encoding.encoding = options["encoding"]
394     if options["encodingmode"]:
395         encoding.encodingmode = options["encodingmode"]
396     if options["time"]:
397         def get_times():
398             t = os.times()
399             if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
400                 t = (t[0], t[1], t[2], t[3], time.clock())
401             return t
402         s = get_times()
403         def print_time():
404             t = get_times()
405             ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
406                 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
407         atexit.register(print_time)
408
409     if options['verbose'] or options['debug'] or options['quiet']:
410         ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
411         ui.setconfig('ui', 'debug', str(bool(options['debug'])))
412         ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
413     if options['traceback']:
414         ui.setconfig('ui', 'traceback', 'on')
415     if options['noninteractive']:
416         ui.setconfig('ui', 'interactive', 'off')
417
418     if options['help']:
419         return commands.help_(ui, cmd, options['version'])
420     elif options['version']:
421         return commands.version_(ui)
422     elif not cmd:
423         return commands.help_(ui, 'shortlist')
424
425     repo = None
426     if cmd not in commands.norepo.split():
427         try:
428             repo = hg.repository(ui, path=path)
429             ui = repo.ui
430             if not repo.local():
431                 raise util.Abort(_("repository '%s' is not local") % path)
432             ui.setconfig("bundle", "mainreporoot", repo.root)
433         except error.RepoError:
434             if cmd not in commands.optionalrepo.split():
435                 if args and not path: # try to infer -R from command args
436                     repos = map(_findrepo, args)
437                     guess = repos[0]
438                     if guess and repos.count(guess) == len(repos):
439                         return _dispatch(ui, ['--repository', guess] + fullargs)
440                 if not path:
441                     raise error.RepoError(_("There is no Mercurial repository"
442                                       " here (.hg not found)"))
443                 raise
444         args.insert(0, repo)
445     elif rpath:
446         ui.warn("warning: --repository ignored\n")
447
448     d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
449     return runcommand(lui, repo, cmd, fullargs, ui, options, d)
450
451 def _runcommand(ui, options, cmd, cmdfunc):
452     def checkargs():
453         try:
454             return cmdfunc()
455         except error.SignatureError:
456             raise error.ParseError(cmd, _("invalid arguments"))
457
458     if options['profile']:
459         format = ui.config('profiling', 'format', default='text')
460
461         if not format in ['text', 'kcachegrind']:
462             ui.warn(_("unrecognized profiling format '%s'"
463                         " - Ignored\n") % format)
464             format = 'text'
465
466         output = ui.config('profiling', 'output')
467
468         if output:
469             path = os.path.expanduser(output)
470             path = ui.expandpath(path)
471             ostream = open(path, 'wb')
472         else:
473             ostream = sys.stderr
474
475         try:
476             from mercurial import lsprof
477         except ImportError:
478             raise util.Abort(_(
479                 'lsprof not available - install from '
480                 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
481         p = lsprof.Profiler()
482         p.enable(subcalls=True)
483         try:
484             return checkargs()
485         finally:
486             p.disable()
487
488             if format == 'kcachegrind':
489                 import lsprofcalltree
490                 calltree = lsprofcalltree.KCacheGrind(p)
491                 calltree.output(ostream)
492             else:
493                 # format == 'text'
494                 stats = lsprof.Stats(p.getstats())
495                 stats.sort()
496                 stats.pprint(top=10, file=ostream, climit=5)
497
498             if output:
499                 ostream.close()
500     else:
501         return checkargs()