Mercurial > public > mercurial-scm > hg
comparison mercurial/cmdutil.py @ 4549:0c61124ad877
dispatch: move dispatching code to cmdutil
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Mon, 11 Jun 2007 21:09:24 -0500 |
parents | c9fcebbfc422 |
children | 6ed91894261e |
comparison
equal
deleted
inserted
replaced
4548:c9fcebbfc422 | 4549:0c61124ad877 |
---|---|
5 # This software may be used and distributed according to the terms | 5 # This software may be used and distributed according to the terms |
6 # of the GNU General Public License, incorporated herein by reference. | 6 # of the GNU General Public License, incorporated herein by reference. |
7 | 7 |
8 from node import * | 8 from node import * |
9 from i18n import _ | 9 from i18n import _ |
10 import os, sys, mdiff, bdiff, util, templater, patch | 10 import os, sys, mdiff, bdiff, util, templater, patch, commands |
11 import atexit, signal, pdb, hg, lock, fancyopts, traceback | |
12 import socket, revlog, version, extensions, errno | |
11 | 13 |
12 revrangesep = ':' | 14 revrangesep = ':' |
15 | |
16 class UnknownCommand(Exception): | |
17 """Exception raised if command is not in the command table.""" | |
18 class AmbiguousCommand(Exception): | |
19 """Exception raised if command shortcut matches more than one command.""" | |
20 class ParseError(Exception): | |
21 """Exception raised on errors in parsing the command line.""" | |
22 | |
23 def runcatch(u, args): | |
24 def catchterm(*args): | |
25 raise util.SignalInterrupt | |
26 | |
27 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': | |
28 num = getattr(signal, name, None) | |
29 if num: signal.signal(num, catchterm) | |
30 | |
31 try: | |
32 return dispatch(u, args) | |
33 except ParseError, inst: | |
34 if inst.args[0]: | |
35 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1])) | |
36 commands.help_(u, inst.args[0]) | |
37 else: | |
38 u.warn(_("hg: %s\n") % inst.args[1]) | |
39 commands.help_(u, 'shortlist') | |
40 except AmbiguousCommand, inst: | |
41 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") % | |
42 (inst.args[0], " ".join(inst.args[1]))) | |
43 except UnknownCommand, inst: | |
44 u.warn(_("hg: unknown command '%s'\n") % inst.args[0]) | |
45 commands.help_(u, 'shortlist') | |
46 except hg.RepoError, inst: | |
47 u.warn(_("abort: %s!\n") % inst) | |
48 except lock.LockHeld, inst: | |
49 if inst.errno == errno.ETIMEDOUT: | |
50 reason = _('timed out waiting for lock held by %s') % inst.locker | |
51 else: | |
52 reason = _('lock held by %s') % inst.locker | |
53 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason)) | |
54 except lock.LockUnavailable, inst: | |
55 u.warn(_("abort: could not lock %s: %s\n") % | |
56 (inst.desc or inst.filename, inst.strerror)) | |
57 except revlog.RevlogError, inst: | |
58 u.warn(_("abort: %s!\n") % inst) | |
59 except util.SignalInterrupt: | |
60 u.warn(_("killed!\n")) | |
61 except KeyboardInterrupt: | |
62 try: | |
63 u.warn(_("interrupted!\n")) | |
64 except IOError, inst: | |
65 if inst.errno == errno.EPIPE: | |
66 if u.debugflag: | |
67 u.warn(_("\nbroken pipe\n")) | |
68 else: | |
69 raise | |
70 except socket.error, inst: | |
71 u.warn(_("abort: %s\n") % inst[1]) | |
72 except IOError, inst: | |
73 if hasattr(inst, "code"): | |
74 u.warn(_("abort: %s\n") % inst) | |
75 elif hasattr(inst, "reason"): | |
76 try: # usually it is in the form (errno, strerror) | |
77 reason = inst.reason.args[1] | |
78 except: # it might be anything, for example a string | |
79 reason = inst.reason | |
80 u.warn(_("abort: error: %s\n") % reason) | |
81 elif hasattr(inst, "args") and inst[0] == errno.EPIPE: | |
82 if u.debugflag: | |
83 u.warn(_("broken pipe\n")) | |
84 elif getattr(inst, "strerror", None): | |
85 if getattr(inst, "filename", None): | |
86 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename)) | |
87 else: | |
88 u.warn(_("abort: %s\n") % inst.strerror) | |
89 else: | |
90 raise | |
91 except OSError, inst: | |
92 if getattr(inst, "filename", None): | |
93 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename)) | |
94 else: | |
95 u.warn(_("abort: %s\n") % inst.strerror) | |
96 except util.UnexpectedOutput, inst: | |
97 u.warn(_("abort: %s") % inst[0]) | |
98 if not isinstance(inst[1], basestring): | |
99 u.warn(" %r\n" % (inst[1],)) | |
100 elif not inst[1]: | |
101 u.warn(_(" empty string\n")) | |
102 else: | |
103 u.warn("\n%r\n" % util.ellipsis(inst[1])) | |
104 except util.Abort, inst: | |
105 u.warn(_("abort: %s\n") % inst) | |
106 except TypeError, inst: | |
107 # was this an argument error? | |
108 tb = traceback.extract_tb(sys.exc_info()[2]) | |
109 if len(tb) > 2: # no | |
110 raise | |
111 u.debug(inst, "\n") | |
112 u.warn(_("%s: invalid arguments\n") % cmd) | |
113 commands.help_(u, cmd) | |
114 except SystemExit, inst: | |
115 # Commands shouldn't sys.exit directly, but give a return code. | |
116 # Just in case catch this and and pass exit code to caller. | |
117 return inst.code | |
118 except: | |
119 u.warn(_("** unknown exception encountered, details follow\n")) | |
120 u.warn(_("** report bug details to " | |
121 "http://www.selenic.com/mercurial/bts\n")) | |
122 u.warn(_("** or mercurial@selenic.com\n")) | |
123 u.warn(_("** Mercurial Distributed SCM (version %s)\n") | |
124 % version.get_version()) | |
125 raise | |
126 | |
127 return -1 | |
128 | |
129 def findpossible(ui, cmd): | |
130 """ | |
131 Return cmd -> (aliases, command table entry) | |
132 for each matching command. | |
133 Return debug commands (or their aliases) only if no normal command matches. | |
134 """ | |
135 choice = {} | |
136 debugchoice = {} | |
137 for e in commands.table.keys(): | |
138 aliases = e.lstrip("^").split("|") | |
139 found = None | |
140 if cmd in aliases: | |
141 found = cmd | |
142 elif not ui.config("ui", "strict"): | |
143 for a in aliases: | |
144 if a.startswith(cmd): | |
145 found = a | |
146 break | |
147 if found is not None: | |
148 if aliases[0].startswith("debug") or found.startswith("debug"): | |
149 debugchoice[found] = (aliases, commands.table[e]) | |
150 else: | |
151 choice[found] = (aliases, commands.table[e]) | |
152 | |
153 if not choice and debugchoice: | |
154 choice = debugchoice | |
155 | |
156 return choice | |
157 | |
158 def findcmd(ui, cmd): | |
159 """Return (aliases, command table entry) for command string.""" | |
160 choice = findpossible(ui, cmd) | |
161 | |
162 if choice.has_key(cmd): | |
163 return choice[cmd] | |
164 | |
165 if len(choice) > 1: | |
166 clist = choice.keys() | |
167 clist.sort() | |
168 raise AmbiguousCommand(cmd, clist) | |
169 | |
170 if choice: | |
171 return choice.values()[0] | |
172 | |
173 raise UnknownCommand(cmd) | |
174 | |
175 def parse(ui, args): | |
176 options = {} | |
177 cmdoptions = {} | |
178 | |
179 try: | |
180 args = fancyopts.fancyopts(args, commands.globalopts, options) | |
181 except fancyopts.getopt.GetoptError, inst: | |
182 raise ParseError(None, inst) | |
183 | |
184 if args: | |
185 cmd, args = args[0], args[1:] | |
186 aliases, i = findcmd(ui, cmd) | |
187 cmd = aliases[0] | |
188 defaults = ui.config("defaults", cmd) | |
189 if defaults: | |
190 args = shlex.split(defaults) + args | |
191 c = list(i[1]) | |
192 else: | |
193 cmd = None | |
194 c = [] | |
195 | |
196 # combine global options into local | |
197 for o in commands.globalopts: | |
198 c.append((o[0], o[1], options[o[1]], o[3])) | |
199 | |
200 try: | |
201 args = fancyopts.fancyopts(args, c, cmdoptions) | |
202 except fancyopts.getopt.GetoptError, inst: | |
203 raise ParseError(cmd, inst) | |
204 | |
205 # separate global options back out | |
206 for o in commands.globalopts: | |
207 n = o[1] | |
208 options[n] = cmdoptions[n] | |
209 del cmdoptions[n] | |
210 | |
211 return (cmd, cmd and i[0] or None, args, options, cmdoptions) | |
212 | |
213 def parseconfig(config): | |
214 """parse the --config options from the command line""" | |
215 parsed = [] | |
216 for cfg in config: | |
217 try: | |
218 name, value = cfg.split('=', 1) | |
219 section, name = name.split('.', 1) | |
220 if not section or not name: | |
221 raise IndexError | |
222 parsed.append((section, name, value)) | |
223 except (IndexError, ValueError): | |
224 raise util.Abort(_('malformed --config option: %s') % cfg) | |
225 return parsed | |
226 | |
227 def dispatch(u, args): | |
228 extensions.loadall(u) | |
229 u.addreadhook(extensions.loadall) | |
230 | |
231 cmd, func, args, options, cmdoptions = parse(u, args) | |
232 | |
233 if options["encoding"]: | |
234 util._encoding = options["encoding"] | |
235 if options["encodingmode"]: | |
236 util._encodingmode = options["encodingmode"] | |
237 if options["time"]: | |
238 def get_times(): | |
239 t = os.times() | |
240 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock() | |
241 t = (t[0], t[1], t[2], t[3], time.clock()) | |
242 return t | |
243 s = get_times() | |
244 def print_time(): | |
245 t = get_times() | |
246 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") % | |
247 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3])) | |
248 atexit.register(print_time) | |
249 | |
250 if options['cwd']: | |
251 os.chdir(options['cwd']) | |
252 | |
253 u.updateopts(options["verbose"], options["debug"], options["quiet"], | |
254 not options["noninteractive"], options["traceback"], | |
255 parseconfig(options["config"])) | |
256 | |
257 path = u.expandpath(options["repository"]) or "" | |
258 repo = path and hg.repository(u, path=path) or None | |
259 if repo and not repo.local(): | |
260 raise util.Abort(_("repository '%s' is not local") % path) | |
261 | |
262 if options['help']: | |
263 return commands.help_(u, cmd, options['version']) | |
264 elif options['version']: | |
265 return commands.version_(u) | |
266 elif not cmd: | |
267 return commands.help_(u, 'shortlist') | |
268 | |
269 if cmd not in commands.norepo.split(): | |
270 try: | |
271 if not repo: | |
272 repo = hg.repository(u, path=path) | |
273 u = repo.ui | |
274 except hg.RepoError: | |
275 if cmd not in commands.optionalrepo.split(): | |
276 raise | |
277 d = lambda: func(u, repo, *args, **cmdoptions) | |
278 else: | |
279 d = lambda: func(u, *args, **cmdoptions) | |
280 | |
281 return runcommand(u, options, d) | |
13 | 282 |
14 def runcommand(u, options, d): | 283 def runcommand(u, options, d): |
15 # enter the debugger before command execution | 284 # enter the debugger before command execution |
16 if options['debugger']: | 285 if options['debugger']: |
17 pdb.set_trace() | 286 pdb.set_trace() |
61 # enter the debugger when we hit an exception | 330 # enter the debugger when we hit an exception |
62 if options['debugger']: | 331 if options['debugger']: |
63 pdb.post_mortem(sys.exc_info()[2]) | 332 pdb.post_mortem(sys.exc_info()[2]) |
64 u.print_exc() | 333 u.print_exc() |
65 raise | 334 raise |
335 | |
336 def bail_if_changed(repo): | |
337 modified, added, removed, deleted = repo.status()[:4] | |
338 if modified or added or removed or deleted: | |
339 raise util.Abort(_("outstanding uncommitted changes")) | |
340 | |
341 def logmessage(opts): | |
342 """ get the log message according to -m and -l option """ | |
343 message = opts['message'] | |
344 logfile = opts['logfile'] | |
345 | |
346 if message and logfile: | |
347 raise util.Abort(_('options --message and --logfile are mutually ' | |
348 'exclusive')) | |
349 if not message and logfile: | |
350 try: | |
351 if logfile == '-': | |
352 message = sys.stdin.read() | |
353 else: | |
354 message = open(logfile).read() | |
355 except IOError, inst: | |
356 raise util.Abort(_("can't read commit message '%s': %s") % | |
357 (logfile, inst.strerror)) | |
358 return message | |
359 | |
360 def setremoteconfig(ui, opts): | |
361 "copy remote options to ui tree" | |
362 if opts.get('ssh'): | |
363 ui.setconfig("ui", "ssh", opts['ssh']) | |
364 if opts.get('remotecmd'): | |
365 ui.setconfig("ui", "remotecmd", opts['remotecmd']) | |
66 | 366 |
67 def parseurl(url, revs): | 367 def parseurl(url, revs): |
68 '''parse url#branch, returning url, branch + revs''' | 368 '''parse url#branch, returning url, branch + revs''' |
69 | 369 |
70 if '#' not in url: | 370 if '#' not in url: |