##// END OF EJS Templates
error: make InterventionRequired take a hint
timeless -
r27628:707d66af default
parent child Browse files
Show More
@@ -1,1046 +1,1048 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 hg,
35 35 hook,
36 36 ui as uimod,
37 37 util,
38 38 )
39 39
40 40 class request(object):
41 41 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
42 42 ferr=None):
43 43 self.args = args
44 44 self.ui = ui
45 45 self.repo = repo
46 46
47 47 # input/output/error streams
48 48 self.fin = fin
49 49 self.fout = fout
50 50 self.ferr = ferr
51 51
52 52 def run():
53 53 "run the command in sys.argv"
54 54 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
55 55
56 56 def _getsimilar(symbols, value):
57 57 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
58 58 # The cutoff for similarity here is pretty arbitrary. It should
59 59 # probably be investigated and tweaked.
60 60 return [s for s in symbols if sim(s) > 0.6]
61 61
62 62 def _reportsimilar(write, similar):
63 63 if len(similar) == 1:
64 64 write(_("(did you mean %s?)\n") % similar[0])
65 65 elif similar:
66 66 ss = ", ".join(sorted(similar))
67 67 write(_("(did you mean one of %s?)\n") % ss)
68 68
69 69 def _formatparse(write, inst):
70 70 similar = []
71 71 if isinstance(inst, error.UnknownIdentifier):
72 72 # make sure to check fileset first, as revset can invoke fileset
73 73 similar = _getsimilar(inst.symbols, inst.function)
74 74 if len(inst.args) > 1:
75 75 write(_("hg: parse error at %s: %s\n") %
76 76 (inst.args[1], inst.args[0]))
77 77 if (inst.args[0][0] == ' '):
78 78 write(_("unexpected leading whitespace\n"))
79 79 else:
80 80 write(_("hg: parse error: %s\n") % inst.args[0])
81 81 _reportsimilar(write, similar)
82 82
83 83 def dispatch(req):
84 84 "run the command specified in req.args"
85 85 if req.ferr:
86 86 ferr = req.ferr
87 87 elif req.ui:
88 88 ferr = req.ui.ferr
89 89 else:
90 90 ferr = sys.stderr
91 91
92 92 try:
93 93 if not req.ui:
94 94 req.ui = uimod.ui()
95 95 if '--traceback' in req.args:
96 96 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
97 97
98 98 # set ui streams from the request
99 99 if req.fin:
100 100 req.ui.fin = req.fin
101 101 if req.fout:
102 102 req.ui.fout = req.fout
103 103 if req.ferr:
104 104 req.ui.ferr = req.ferr
105 105 except error.Abort as inst:
106 106 ferr.write(_("abort: %s\n") % inst)
107 107 if inst.hint:
108 108 ferr.write(_("(%s)\n") % inst.hint)
109 109 return -1
110 110 except error.ParseError as inst:
111 111 _formatparse(ferr.write, inst)
112 112 if inst.hint:
113 113 ferr.write(_("(%s)\n") % inst.hint)
114 114 return -1
115 115
116 116 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
117 117 starttime = time.time()
118 118 ret = None
119 119 try:
120 120 ret = _runcatch(req)
121 121 return ret
122 122 finally:
123 123 duration = time.time() - starttime
124 124 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
125 125 msg, ret or 0, duration)
126 126
127 127 def _runcatch(req):
128 128 def catchterm(*args):
129 129 raise error.SignalInterrupt
130 130
131 131 ui = req.ui
132 132 try:
133 133 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
134 134 num = getattr(signal, name, None)
135 135 if num:
136 136 signal.signal(num, catchterm)
137 137 except ValueError:
138 138 pass # happens if called in a thread
139 139
140 140 try:
141 141 try:
142 142 debugger = 'pdb'
143 143 debugtrace = {
144 144 'pdb' : pdb.set_trace
145 145 }
146 146 debugmortem = {
147 147 'pdb' : pdb.post_mortem
148 148 }
149 149
150 150 # read --config before doing anything else
151 151 # (e.g. to change trust settings for reading .hg/hgrc)
152 152 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
153 153
154 154 if req.repo:
155 155 # copy configs that were passed on the cmdline (--config) to
156 156 # the repo ui
157 157 for sec, name, val in cfgs:
158 158 req.repo.ui.setconfig(sec, name, val, source='--config')
159 159
160 160 # developer config: ui.debugger
161 161 debugger = ui.config("ui", "debugger")
162 162 debugmod = pdb
163 163 if not debugger or ui.plain():
164 164 # if we are in HGPLAIN mode, then disable custom debugging
165 165 debugger = 'pdb'
166 166 elif '--debugger' in req.args:
167 167 # This import can be slow for fancy debuggers, so only
168 168 # do it when absolutely necessary, i.e. when actual
169 169 # debugging has been requested
170 170 with demandimport.deactivated():
171 171 try:
172 172 debugmod = __import__(debugger)
173 173 except ImportError:
174 174 pass # Leave debugmod = pdb
175 175
176 176 debugtrace[debugger] = debugmod.set_trace
177 177 debugmortem[debugger] = debugmod.post_mortem
178 178
179 179 # enter the debugger before command execution
180 180 if '--debugger' in req.args:
181 181 ui.warn(_("entering debugger - "
182 182 "type c to continue starting hg or h for help\n"))
183 183
184 184 if (debugger != 'pdb' and
185 185 debugtrace[debugger] == debugtrace['pdb']):
186 186 ui.warn(_("%s debugger specified "
187 187 "but its module was not found\n") % debugger)
188 188 with demandimport.deactivated():
189 189 debugtrace[debugger]()
190 190 try:
191 191 return _dispatch(req)
192 192 finally:
193 193 ui.flush()
194 194 except: # re-raises
195 195 # enter the debugger when we hit an exception
196 196 if '--debugger' in req.args:
197 197 traceback.print_exc()
198 198 debugmortem[debugger](sys.exc_info()[2])
199 199 ui.traceback()
200 200 raise
201 201
202 202 # Global exception handling, alphabetically
203 203 # Mercurial-specific first, followed by built-in and library exceptions
204 204 except error.AmbiguousCommand as inst:
205 205 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
206 206 (inst.args[0], " ".join(inst.args[1])))
207 207 except error.ParseError as inst:
208 208 _formatparse(ui.warn, inst)
209 209 if inst.hint:
210 210 ui.warn(_("(%s)\n") % inst.hint)
211 211 return -1
212 212 except error.LockHeld as inst:
213 213 if inst.errno == errno.ETIMEDOUT:
214 214 reason = _('timed out waiting for lock held by %s') % inst.locker
215 215 else:
216 216 reason = _('lock held by %s') % inst.locker
217 217 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
218 218 except error.LockUnavailable as inst:
219 219 ui.warn(_("abort: could not lock %s: %s\n") %
220 220 (inst.desc or inst.filename, inst.strerror))
221 221 except error.CommandError as inst:
222 222 if inst.args[0]:
223 223 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
224 224 commands.help_(ui, inst.args[0], full=False, command=True)
225 225 else:
226 226 ui.warn(_("hg: %s\n") % inst.args[1])
227 227 commands.help_(ui, 'shortlist')
228 228 except error.OutOfBandError as inst:
229 229 if inst.args:
230 230 msg = _("abort: remote error:\n")
231 231 else:
232 232 msg = _("abort: remote error\n")
233 233 ui.warn(msg)
234 234 if inst.args:
235 235 ui.warn(''.join(inst.args))
236 236 if inst.hint:
237 237 ui.warn('(%s)\n' % inst.hint)
238 238 except error.RepoError as inst:
239 239 ui.warn(_("abort: %s!\n") % inst)
240 240 if inst.hint:
241 241 ui.warn(_("(%s)\n") % inst.hint)
242 242 except error.ResponseError as inst:
243 243 ui.warn(_("abort: %s") % inst.args[0])
244 244 if not isinstance(inst.args[1], basestring):
245 245 ui.warn(" %r\n" % (inst.args[1],))
246 246 elif not inst.args[1]:
247 247 ui.warn(_(" empty string\n"))
248 248 else:
249 249 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
250 250 except error.CensoredNodeError as inst:
251 251 ui.warn(_("abort: file censored %s!\n") % inst)
252 252 except error.RevlogError as inst:
253 253 ui.warn(_("abort: %s!\n") % inst)
254 254 except error.SignalInterrupt:
255 255 ui.warn(_("killed!\n"))
256 256 except error.UnknownCommand as inst:
257 257 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
258 258 try:
259 259 # check if the command is in a disabled extension
260 260 # (but don't check for extensions themselves)
261 261 commands.help_(ui, inst.args[0], unknowncmd=True)
262 262 except (error.UnknownCommand, error.Abort):
263 263 suggested = False
264 264 if len(inst.args) == 2:
265 265 sim = _getsimilar(inst.args[1], inst.args[0])
266 266 if sim:
267 267 _reportsimilar(ui.warn, sim)
268 268 suggested = True
269 269 if not suggested:
270 270 commands.help_(ui, 'shortlist')
271 271 except error.InterventionRequired as inst:
272 272 ui.warn("%s\n" % inst)
273 if inst.hint:
274 ui.warn(_("(%s)\n") % inst.hint)
273 275 return 1
274 276 except error.Abort as inst:
275 277 ui.warn(_("abort: %s\n") % inst)
276 278 if inst.hint:
277 279 ui.warn(_("(%s)\n") % inst.hint)
278 280 except ImportError as inst:
279 281 ui.warn(_("abort: %s!\n") % inst)
280 282 m = str(inst).split()[-1]
281 283 if m in "mpatch bdiff".split():
282 284 ui.warn(_("(did you forget to compile extensions?)\n"))
283 285 elif m in "zlib".split():
284 286 ui.warn(_("(is your Python install correct?)\n"))
285 287 except IOError as inst:
286 288 if util.safehasattr(inst, "code"):
287 289 ui.warn(_("abort: %s\n") % inst)
288 290 elif util.safehasattr(inst, "reason"):
289 291 try: # usually it is in the form (errno, strerror)
290 292 reason = inst.reason.args[1]
291 293 except (AttributeError, IndexError):
292 294 # it might be anything, for example a string
293 295 reason = inst.reason
294 296 if isinstance(reason, unicode):
295 297 # SSLError of Python 2.7.9 contains a unicode
296 298 reason = reason.encode(encoding.encoding, 'replace')
297 299 ui.warn(_("abort: error: %s\n") % reason)
298 300 elif (util.safehasattr(inst, "args")
299 301 and inst.args and inst.args[0] == errno.EPIPE):
300 302 pass
301 303 elif getattr(inst, "strerror", None):
302 304 if getattr(inst, "filename", None):
303 305 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
304 306 else:
305 307 ui.warn(_("abort: %s\n") % inst.strerror)
306 308 else:
307 309 raise
308 310 except OSError as inst:
309 311 if getattr(inst, "filename", None) is not None:
310 312 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
311 313 else:
312 314 ui.warn(_("abort: %s\n") % inst.strerror)
313 315 except KeyboardInterrupt:
314 316 try:
315 317 ui.warn(_("interrupted!\n"))
316 318 except IOError as inst:
317 319 if inst.errno != errno.EPIPE:
318 320 raise
319 321 except MemoryError:
320 322 ui.warn(_("abort: out of memory\n"))
321 323 except SystemExit as inst:
322 324 # Commands shouldn't sys.exit directly, but give a return code.
323 325 # Just in case catch this and and pass exit code to caller.
324 326 return inst.code
325 327 except socket.error as inst:
326 328 ui.warn(_("abort: %s\n") % inst.args[-1])
327 329 except: # re-raises
328 330 # For compatibility checking, we discard the portion of the hg
329 331 # version after the + on the assumption that if a "normal
330 332 # user" is running a build with a + in it the packager
331 333 # probably built from fairly close to a tag and anyone with a
332 334 # 'make local' copy of hg (where the version number can be out
333 335 # of date) will be clueful enough to notice the implausible
334 336 # version number and try updating.
335 337 ct = util.versiontuple(n=2)
336 338 worst = None, ct, ''
337 339 if ui.config('ui', 'supportcontact', None) is None:
338 340 for name, mod in extensions.extensions():
339 341 testedwith = getattr(mod, 'testedwith', '')
340 342 report = getattr(mod, 'buglink', _('the extension author.'))
341 343 if not testedwith.strip():
342 344 # We found an untested extension. It's likely the culprit.
343 345 worst = name, 'unknown', report
344 346 break
345 347
346 348 # Never blame on extensions bundled with Mercurial.
347 349 if testedwith == 'internal':
348 350 continue
349 351
350 352 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
351 353 if ct in tested:
352 354 continue
353 355
354 356 lower = [t for t in tested if t < ct]
355 357 nearest = max(lower or tested)
356 358 if worst[0] is None or nearest < worst[1]:
357 359 worst = name, nearest, report
358 360 if worst[0] is not None:
359 361 name, testedwith, report = worst
360 362 if not isinstance(testedwith, str):
361 363 testedwith = '.'.join([str(c) for c in testedwith])
362 364 warning = (_('** Unknown exception encountered with '
363 365 'possibly-broken third-party extension %s\n'
364 366 '** which supports versions %s of Mercurial.\n'
365 367 '** Please disable %s and try your action again.\n'
366 368 '** If that fixes the bug please report it to %s\n')
367 369 % (name, testedwith, name, report))
368 370 else:
369 371 bugtracker = ui.config('ui', 'supportcontact', None)
370 372 if bugtracker is None:
371 373 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
372 374 warning = (_("** unknown exception encountered, "
373 375 "please report by visiting\n** ") + bugtracker + '\n')
374 376 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
375 377 (_("** Mercurial Distributed SCM (version %s)\n") %
376 378 util.version()) +
377 379 (_("** Extensions loaded: %s\n") %
378 380 ", ".join([x[0] for x in extensions.extensions()])))
379 381 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
380 382 ui.warn(warning)
381 383 raise
382 384
383 385 return -1
384 386
385 387 def aliasargs(fn, givenargs):
386 388 args = getattr(fn, 'args', [])
387 389 if args:
388 390 cmd = ' '.join(map(util.shellquote, args))
389 391
390 392 nums = []
391 393 def replacer(m):
392 394 num = int(m.group(1)) - 1
393 395 nums.append(num)
394 396 if num < len(givenargs):
395 397 return givenargs[num]
396 398 raise error.Abort(_('too few arguments for command alias'))
397 399 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
398 400 givenargs = [x for i, x in enumerate(givenargs)
399 401 if i not in nums]
400 402 args = shlex.split(cmd)
401 403 return args + givenargs
402 404
403 405 def aliasinterpolate(name, args, cmd):
404 406 '''interpolate args into cmd for shell aliases
405 407
406 408 This also handles $0, $@ and "$@".
407 409 '''
408 410 # util.interpolate can't deal with "$@" (with quotes) because it's only
409 411 # built to match prefix + patterns.
410 412 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
411 413 replacemap['$0'] = name
412 414 replacemap['$$'] = '$'
413 415 replacemap['$@'] = ' '.join(args)
414 416 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
415 417 # parameters, separated out into words. Emulate the same behavior here by
416 418 # quoting the arguments individually. POSIX shells will then typically
417 419 # tokenize each argument into exactly one word.
418 420 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
419 421 # escape '\$' for regex
420 422 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
421 423 r = re.compile(regex)
422 424 return r.sub(lambda x: replacemap[x.group()], cmd)
423 425
424 426 class cmdalias(object):
425 427 def __init__(self, name, definition, cmdtable):
426 428 self.name = self.cmd = name
427 429 self.cmdname = ''
428 430 self.definition = definition
429 431 self.fn = None
430 432 self.args = []
431 433 self.opts = []
432 434 self.help = ''
433 435 self.norepo = True
434 436 self.optionalrepo = False
435 437 self.badalias = None
436 438 self.unknowncmd = False
437 439
438 440 try:
439 441 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
440 442 for alias, e in cmdtable.iteritems():
441 443 if e is entry:
442 444 self.cmd = alias
443 445 break
444 446 self.shadows = True
445 447 except error.UnknownCommand:
446 448 self.shadows = False
447 449
448 450 if not self.definition:
449 451 self.badalias = _("no definition for alias '%s'") % self.name
450 452 return
451 453
452 454 if self.definition.startswith('!'):
453 455 self.shell = True
454 456 def fn(ui, *args):
455 457 env = {'HG_ARGS': ' '.join((self.name,) + args)}
456 458 def _checkvar(m):
457 459 if m.groups()[0] == '$':
458 460 return m.group()
459 461 elif int(m.groups()[0]) <= len(args):
460 462 return m.group()
461 463 else:
462 464 ui.debug("No argument found for substitution "
463 465 "of %i variable in alias '%s' definition."
464 466 % (int(m.groups()[0]), self.name))
465 467 return ''
466 468 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
467 469 cmd = aliasinterpolate(self.name, args, cmd)
468 470 return ui.system(cmd, environ=env)
469 471 self.fn = fn
470 472 return
471 473
472 474 try:
473 475 args = shlex.split(self.definition)
474 476 except ValueError as inst:
475 477 self.badalias = (_("error in definition for alias '%s': %s")
476 478 % (self.name, inst))
477 479 return
478 480 self.cmdname = cmd = args.pop(0)
479 481 args = map(util.expandpath, args)
480 482
481 483 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
482 484 if _earlygetopt([invalidarg], args):
483 485 self.badalias = (_("error in definition for alias '%s': %s may "
484 486 "only be given on the command line")
485 487 % (self.name, invalidarg))
486 488 return
487 489
488 490 try:
489 491 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
490 492 if len(tableentry) > 2:
491 493 self.fn, self.opts, self.help = tableentry
492 494 else:
493 495 self.fn, self.opts = tableentry
494 496
495 497 self.args = aliasargs(self.fn, args)
496 498 if cmd not in commands.norepo.split(' '):
497 499 self.norepo = False
498 500 if cmd in commands.optionalrepo.split(' '):
499 501 self.optionalrepo = True
500 502 if self.help.startswith("hg " + cmd):
501 503 # drop prefix in old-style help lines so hg shows the alias
502 504 self.help = self.help[4 + len(cmd):]
503 505 self.__doc__ = self.fn.__doc__
504 506
505 507 except error.UnknownCommand:
506 508 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
507 509 % (self.name, cmd))
508 510 self.unknowncmd = True
509 511 except error.AmbiguousCommand:
510 512 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
511 513 % (self.name, cmd))
512 514
513 515 def __call__(self, ui, *args, **opts):
514 516 if self.badalias:
515 517 hint = None
516 518 if self.unknowncmd:
517 519 try:
518 520 # check if the command is in a disabled extension
519 521 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
520 522 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
521 523 except error.UnknownCommand:
522 524 pass
523 525 raise error.Abort(self.badalias, hint=hint)
524 526 if self.shadows:
525 527 ui.debug("alias '%s' shadows command '%s'\n" %
526 528 (self.name, self.cmdname))
527 529
528 530 if util.safehasattr(self, 'shell'):
529 531 return self.fn(ui, *args, **opts)
530 532 else:
531 533 try:
532 534 return util.checksignature(self.fn)(ui, *args, **opts)
533 535 except error.SignatureError:
534 536 args = ' '.join([self.cmdname] + self.args)
535 537 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
536 538 raise
537 539
538 540 def addaliases(ui, cmdtable):
539 541 # aliases are processed after extensions have been loaded, so they
540 542 # may use extension commands. Aliases can also use other alias definitions,
541 543 # but only if they have been defined prior to the current definition.
542 544 for alias, definition in ui.configitems('alias'):
543 545 aliasdef = cmdalias(alias, definition, cmdtable)
544 546
545 547 try:
546 548 olddef = cmdtable[aliasdef.cmd][0]
547 549 if olddef.definition == aliasdef.definition:
548 550 continue
549 551 except (KeyError, AttributeError):
550 552 # definition might not exist or it might not be a cmdalias
551 553 pass
552 554
553 555 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
554 556 if aliasdef.norepo:
555 557 commands.norepo += ' %s' % alias
556 558 if aliasdef.optionalrepo:
557 559 commands.optionalrepo += ' %s' % alias
558 560
559 561 def _parse(ui, args):
560 562 options = {}
561 563 cmdoptions = {}
562 564
563 565 try:
564 566 args = fancyopts.fancyopts(args, commands.globalopts, options)
565 567 except fancyopts.getopt.GetoptError as inst:
566 568 raise error.CommandError(None, inst)
567 569
568 570 if args:
569 571 cmd, args = args[0], args[1:]
570 572 aliases, entry = cmdutil.findcmd(cmd, commands.table,
571 573 ui.configbool("ui", "strict"))
572 574 cmd = aliases[0]
573 575 args = aliasargs(entry[0], args)
574 576 defaults = ui.config("defaults", cmd)
575 577 if defaults:
576 578 args = map(util.expandpath, shlex.split(defaults)) + args
577 579 c = list(entry[1])
578 580 else:
579 581 cmd = None
580 582 c = []
581 583
582 584 # combine global options into local
583 585 for o in commands.globalopts:
584 586 c.append((o[0], o[1], options[o[1]], o[3]))
585 587
586 588 try:
587 589 args = fancyopts.fancyopts(args, c, cmdoptions, True)
588 590 except fancyopts.getopt.GetoptError as inst:
589 591 raise error.CommandError(cmd, inst)
590 592
591 593 # separate global options back out
592 594 for o in commands.globalopts:
593 595 n = o[1]
594 596 options[n] = cmdoptions[n]
595 597 del cmdoptions[n]
596 598
597 599 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
598 600
599 601 def _parseconfig(ui, config):
600 602 """parse the --config options from the command line"""
601 603 configs = []
602 604
603 605 for cfg in config:
604 606 try:
605 607 name, value = cfg.split('=', 1)
606 608 section, name = name.split('.', 1)
607 609 if not section or not name:
608 610 raise IndexError
609 611 ui.setconfig(section, name, value, '--config')
610 612 configs.append((section, name, value))
611 613 except (IndexError, ValueError):
612 614 raise error.Abort(_('malformed --config option: %r '
613 615 '(use --config section.name=value)') % cfg)
614 616
615 617 return configs
616 618
617 619 def _earlygetopt(aliases, args):
618 620 """Return list of values for an option (or aliases).
619 621
620 622 The values are listed in the order they appear in args.
621 623 The options and values are removed from args.
622 624
623 625 >>> args = ['x', '--cwd', 'foo', 'y']
624 626 >>> _earlygetopt(['--cwd'], args), args
625 627 (['foo'], ['x', 'y'])
626 628
627 629 >>> args = ['x', '--cwd=bar', 'y']
628 630 >>> _earlygetopt(['--cwd'], args), args
629 631 (['bar'], ['x', 'y'])
630 632
631 633 >>> args = ['x', '-R', 'foo', 'y']
632 634 >>> _earlygetopt(['-R'], args), args
633 635 (['foo'], ['x', 'y'])
634 636
635 637 >>> args = ['x', '-Rbar', 'y']
636 638 >>> _earlygetopt(['-R'], args), args
637 639 (['bar'], ['x', 'y'])
638 640 """
639 641 try:
640 642 argcount = args.index("--")
641 643 except ValueError:
642 644 argcount = len(args)
643 645 shortopts = [opt for opt in aliases if len(opt) == 2]
644 646 values = []
645 647 pos = 0
646 648 while pos < argcount:
647 649 fullarg = arg = args[pos]
648 650 equals = arg.find('=')
649 651 if equals > -1:
650 652 arg = arg[:equals]
651 653 if arg in aliases:
652 654 del args[pos]
653 655 if equals > -1:
654 656 values.append(fullarg[equals + 1:])
655 657 argcount -= 1
656 658 else:
657 659 if pos + 1 >= argcount:
658 660 # ignore and let getopt report an error if there is no value
659 661 break
660 662 values.append(args.pop(pos))
661 663 argcount -= 2
662 664 elif arg[:2] in shortopts:
663 665 # short option can have no following space, e.g. hg log -Rfoo
664 666 values.append(args.pop(pos)[2:])
665 667 argcount -= 1
666 668 else:
667 669 pos += 1
668 670 return values
669 671
670 672 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
671 673 # run pre-hook, and abort if it fails
672 674 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
673 675 pats=cmdpats, opts=cmdoptions)
674 676 ret = _runcommand(ui, options, cmd, d)
675 677 # run post-hook, passing command result
676 678 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
677 679 result=ret, pats=cmdpats, opts=cmdoptions)
678 680 return ret
679 681
680 682 def _getlocal(ui, rpath):
681 683 """Return (path, local ui object) for the given target path.
682 684
683 685 Takes paths in [cwd]/.hg/hgrc into account."
684 686 """
685 687 try:
686 688 wd = os.getcwd()
687 689 except OSError as e:
688 690 raise error.Abort(_("error getting current working directory: %s") %
689 691 e.strerror)
690 692 path = cmdutil.findrepo(wd) or ""
691 693 if not path:
692 694 lui = ui
693 695 else:
694 696 lui = ui.copy()
695 697 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
696 698
697 699 if rpath and rpath[-1]:
698 700 path = lui.expandpath(rpath[-1])
699 701 lui = ui.copy()
700 702 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
701 703
702 704 return path, lui
703 705
704 706 def _checkshellalias(lui, ui, args, precheck=True):
705 707 """Return the function to run the shell alias, if it is required
706 708
707 709 'precheck' is whether this function is invoked before adding
708 710 aliases or not.
709 711 """
710 712 options = {}
711 713
712 714 try:
713 715 args = fancyopts.fancyopts(args, commands.globalopts, options)
714 716 except fancyopts.getopt.GetoptError:
715 717 return
716 718
717 719 if not args:
718 720 return
719 721
720 722 if precheck:
721 723 strict = True
722 724 norepo = commands.norepo
723 725 optionalrepo = commands.optionalrepo
724 726 def restorecommands():
725 727 commands.norepo = norepo
726 728 commands.optionalrepo = optionalrepo
727 729 cmdtable = commands.table.copy()
728 730 addaliases(lui, cmdtable)
729 731 else:
730 732 strict = False
731 733 def restorecommands():
732 734 pass
733 735 cmdtable = commands.table
734 736
735 737 cmd = args[0]
736 738 try:
737 739 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
738 740 except (error.AmbiguousCommand, error.UnknownCommand):
739 741 restorecommands()
740 742 return
741 743
742 744 cmd = aliases[0]
743 745 fn = entry[0]
744 746
745 747 if cmd and util.safehasattr(fn, 'shell'):
746 748 d = lambda: fn(ui, *args[1:])
747 749 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
748 750 [], {})
749 751
750 752 restorecommands()
751 753
752 754 _loaded = set()
753 755 def _dispatch(req):
754 756 args = req.args
755 757 ui = req.ui
756 758
757 759 # check for cwd
758 760 cwd = _earlygetopt(['--cwd'], args)
759 761 if cwd:
760 762 os.chdir(cwd[-1])
761 763
762 764 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
763 765 path, lui = _getlocal(ui, rpath)
764 766
765 767 # Now that we're operating in the right directory/repository with
766 768 # the right config settings, check for shell aliases
767 769 shellaliasfn = _checkshellalias(lui, ui, args)
768 770 if shellaliasfn:
769 771 return shellaliasfn()
770 772
771 773 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
772 774 # reposetup. Programs like TortoiseHg will call _dispatch several
773 775 # times so we keep track of configured extensions in _loaded.
774 776 extensions.loadall(lui)
775 777 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
776 778 # Propagate any changes to lui.__class__ by extensions
777 779 ui.__class__ = lui.__class__
778 780
779 781 # (uisetup and extsetup are handled in extensions.loadall)
780 782
781 783 for name, module in exts:
782 784 cmdtable = getattr(module, 'cmdtable', {})
783 785 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
784 786 if overrides:
785 787 ui.warn(_("extension '%s' overrides commands: %s\n")
786 788 % (name, " ".join(overrides)))
787 789 commands.table.update(cmdtable)
788 790 _loaded.add(name)
789 791
790 792 # (reposetup is handled in hg.repository)
791 793
792 794 addaliases(lui, commands.table)
793 795
794 796 if not lui.configbool("ui", "strict"):
795 797 # All aliases and commands are completely defined, now.
796 798 # Check abbreviation/ambiguity of shell alias again, because shell
797 799 # alias may cause failure of "_parse" (see issue4355)
798 800 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
799 801 if shellaliasfn:
800 802 return shellaliasfn()
801 803
802 804 # check for fallback encoding
803 805 fallback = lui.config('ui', 'fallbackencoding')
804 806 if fallback:
805 807 encoding.fallbackencoding = fallback
806 808
807 809 fullargs = args
808 810 cmd, func, args, options, cmdoptions = _parse(lui, args)
809 811
810 812 if options["config"]:
811 813 raise error.Abort(_("option --config may not be abbreviated!"))
812 814 if options["cwd"]:
813 815 raise error.Abort(_("option --cwd may not be abbreviated!"))
814 816 if options["repository"]:
815 817 raise error.Abort(_(
816 818 "option -R has to be separated from other options (e.g. not -qR) "
817 819 "and --repository may only be abbreviated as --repo!"))
818 820
819 821 if options["encoding"]:
820 822 encoding.encoding = options["encoding"]
821 823 if options["encodingmode"]:
822 824 encoding.encodingmode = options["encodingmode"]
823 825 if options["time"]:
824 826 def get_times():
825 827 t = os.times()
826 828 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
827 829 t = (t[0], t[1], t[2], t[3], time.clock())
828 830 return t
829 831 s = get_times()
830 832 def print_time():
831 833 t = get_times()
832 834 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
833 835 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
834 836 atexit.register(print_time)
835 837
836 838 uis = set([ui, lui])
837 839
838 840 if req.repo:
839 841 uis.add(req.repo.ui)
840 842
841 843 if options['verbose'] or options['debug'] or options['quiet']:
842 844 for opt in ('verbose', 'debug', 'quiet'):
843 845 val = str(bool(options[opt]))
844 846 for ui_ in uis:
845 847 ui_.setconfig('ui', opt, val, '--' + opt)
846 848
847 849 if options['traceback']:
848 850 for ui_ in uis:
849 851 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
850 852
851 853 if options['noninteractive']:
852 854 for ui_ in uis:
853 855 ui_.setconfig('ui', 'interactive', 'off', '-y')
854 856
855 857 if cmdoptions.get('insecure', False):
856 858 for ui_ in uis:
857 859 ui_.setconfig('web', 'cacerts', '!', '--insecure')
858 860
859 861 if options['version']:
860 862 return commands.version_(ui)
861 863 if options['help']:
862 864 return commands.help_(ui, cmd, command=cmd is not None)
863 865 elif not cmd:
864 866 return commands.help_(ui, 'shortlist')
865 867
866 868 repo = None
867 869 cmdpats = args[:]
868 870 if cmd not in commands.norepo.split():
869 871 # use the repo from the request only if we don't have -R
870 872 if not rpath and not cwd:
871 873 repo = req.repo
872 874
873 875 if repo:
874 876 # set the descriptors of the repo ui to those of ui
875 877 repo.ui.fin = ui.fin
876 878 repo.ui.fout = ui.fout
877 879 repo.ui.ferr = ui.ferr
878 880 else:
879 881 try:
880 882 repo = hg.repository(ui, path=path)
881 883 if not repo.local():
882 884 raise error.Abort(_("repository '%s' is not local") % path)
883 885 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
884 886 except error.RequirementError:
885 887 raise
886 888 except error.RepoError:
887 889 if rpath and rpath[-1]: # invalid -R path
888 890 raise
889 891 if cmd not in commands.optionalrepo.split():
890 892 if (cmd in commands.inferrepo.split() and
891 893 args and not path): # try to infer -R from command args
892 894 repos = map(cmdutil.findrepo, args)
893 895 guess = repos[0]
894 896 if guess and repos.count(guess) == len(repos):
895 897 req.args = ['--repository', guess] + fullargs
896 898 return _dispatch(req)
897 899 if not path:
898 900 raise error.RepoError(_("no repository found in '%s'"
899 901 " (.hg not found)")
900 902 % os.getcwd())
901 903 raise
902 904 if repo:
903 905 ui = repo.ui
904 906 if options['hidden']:
905 907 repo = repo.unfiltered()
906 908 args.insert(0, repo)
907 909 elif rpath:
908 910 ui.warn(_("warning: --repository ignored\n"))
909 911
910 912 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
911 913 ui.log("command", '%s\n', msg)
912 914 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
913 915 try:
914 916 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
915 917 cmdpats, cmdoptions)
916 918 finally:
917 919 if repo and repo != req.repo:
918 920 repo.close()
919 921
920 922 def lsprofile(ui, func, fp):
921 923 format = ui.config('profiling', 'format', default='text')
922 924 field = ui.config('profiling', 'sort', default='inlinetime')
923 925 limit = ui.configint('profiling', 'limit', default=30)
924 926 climit = ui.configint('profiling', 'nested', default=0)
925 927
926 928 if format not in ['text', 'kcachegrind']:
927 929 ui.warn(_("unrecognized profiling format '%s'"
928 930 " - Ignored\n") % format)
929 931 format = 'text'
930 932
931 933 try:
932 934 from . import lsprof
933 935 except ImportError:
934 936 raise error.Abort(_(
935 937 'lsprof not available - install from '
936 938 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
937 939 p = lsprof.Profiler()
938 940 p.enable(subcalls=True)
939 941 try:
940 942 return func()
941 943 finally:
942 944 p.disable()
943 945
944 946 if format == 'kcachegrind':
945 947 from . import lsprofcalltree
946 948 calltree = lsprofcalltree.KCacheGrind(p)
947 949 calltree.output(fp)
948 950 else:
949 951 # format == 'text'
950 952 stats = lsprof.Stats(p.getstats())
951 953 stats.sort(field)
952 954 stats.pprint(limit=limit, file=fp, climit=climit)
953 955
954 956 def flameprofile(ui, func, fp):
955 957 try:
956 958 from flamegraph import flamegraph
957 959 except ImportError:
958 960 raise error.Abort(_(
959 961 'flamegraph not available - install from '
960 962 'https://github.com/evanhempel/python-flamegraph'))
961 963 # developer config: profiling.freq
962 964 freq = ui.configint('profiling', 'freq', default=1000)
963 965 filter_ = None
964 966 collapse_recursion = True
965 967 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
966 968 filter_, collapse_recursion)
967 969 start_time = time.clock()
968 970 try:
969 971 thread.start()
970 972 func()
971 973 finally:
972 974 thread.stop()
973 975 thread.join()
974 976 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
975 977 time.clock() - start_time, thread.num_frames(),
976 978 thread.num_frames(unique=True)))
977 979
978 980
979 981 def statprofile(ui, func, fp):
980 982 try:
981 983 import statprof
982 984 except ImportError:
983 985 raise error.Abort(_(
984 986 'statprof not available - install using "easy_install statprof"'))
985 987
986 988 freq = ui.configint('profiling', 'freq', default=1000)
987 989 if freq > 0:
988 990 statprof.reset(freq)
989 991 else:
990 992 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
991 993
992 994 statprof.start()
993 995 try:
994 996 return func()
995 997 finally:
996 998 statprof.stop()
997 999 statprof.display(fp)
998 1000
999 1001 def _runcommand(ui, options, cmd, cmdfunc):
1000 1002 """Enables the profiler if applicable.
1001 1003
1002 1004 ``profiling.enabled`` - boolean config that enables or disables profiling
1003 1005 """
1004 1006 def checkargs():
1005 1007 try:
1006 1008 return cmdfunc()
1007 1009 except error.SignatureError:
1008 1010 raise error.CommandError(cmd, _("invalid arguments"))
1009 1011
1010 1012 if options['profile'] or ui.configbool('profiling', 'enabled'):
1011 1013 profiler = os.getenv('HGPROF')
1012 1014 if profiler is None:
1013 1015 profiler = ui.config('profiling', 'type', default='ls')
1014 1016 if profiler not in ('ls', 'stat', 'flame'):
1015 1017 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
1016 1018 profiler = 'ls'
1017 1019
1018 1020 output = ui.config('profiling', 'output')
1019 1021
1020 1022 if output == 'blackbox':
1021 1023 import StringIO
1022 1024 fp = StringIO.StringIO()
1023 1025 elif output:
1024 1026 path = ui.expandpath(output)
1025 1027 fp = open(path, 'wb')
1026 1028 else:
1027 1029 fp = sys.stderr
1028 1030
1029 1031 try:
1030 1032 if profiler == 'ls':
1031 1033 return lsprofile(ui, checkargs, fp)
1032 1034 elif profiler == 'flame':
1033 1035 return flameprofile(ui, checkargs, fp)
1034 1036 else:
1035 1037 return statprofile(ui, checkargs, fp)
1036 1038 finally:
1037 1039 if output:
1038 1040 if output == 'blackbox':
1039 1041 val = "Profile:\n%s" % fp.getvalue()
1040 1042 # ui.log treats the input as a format string,
1041 1043 # so we need to escape any % signs.
1042 1044 val = val.replace('%', '%%')
1043 1045 ui.log('profile', val)
1044 1046 fp.close()
1045 1047 else:
1046 1048 return checkargs()
@@ -1,228 +1,228 b''
1 1 # error.py - Mercurial exceptions
2 2 #
3 3 # Copyright 2005-2008 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 """Mercurial exceptions.
9 9
10 10 This allows us to catch exceptions at higher levels without forcing
11 11 imports.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 # Do not import anything here, please
17 17
18 18 class HintException(Exception):
19 19 def __init__(self, *args, **kw):
20 20 Exception.__init__(self, *args)
21 21 self.hint = kw.get('hint')
22 22
23 23 class RevlogError(HintException):
24 24 pass
25 25
26 26 class FilteredIndexError(IndexError):
27 27 pass
28 28
29 29 class LookupError(RevlogError, KeyError):
30 30 def __init__(self, name, index, message):
31 31 self.name = name
32 32 self.index = index
33 33 # this can't be called 'message' because at least some installs of
34 34 # Python 2.6+ complain about the 'message' property being deprecated
35 35 self.lookupmessage = message
36 36 if isinstance(name, str) and len(name) == 20:
37 37 from .node import short
38 38 name = short(name)
39 39 RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
40 40
41 41 def __str__(self):
42 42 return RevlogError.__str__(self)
43 43
44 44 class FilteredLookupError(LookupError):
45 45 pass
46 46
47 47 class ManifestLookupError(LookupError):
48 48 pass
49 49
50 50 class CommandError(Exception):
51 51 """Exception raised on errors in parsing the command line."""
52 52
53 class InterventionRequired(Exception):
53 class InterventionRequired(HintException):
54 54 """Exception raised when a command requires human intervention."""
55 55
56 56 class Abort(HintException):
57 57 """Raised if a command needs to print an error and exit."""
58 58
59 59 class HookLoadError(Abort):
60 60 """raised when loading a hook fails, aborting an operation
61 61
62 62 Exists to allow more specialized catching."""
63 63
64 64 class HookAbort(Abort):
65 65 """raised when a validation hook fails, aborting an operation
66 66
67 67 Exists to allow more specialized catching."""
68 68
69 69 class ConfigError(Abort):
70 70 """Exception raised when parsing config files"""
71 71
72 72 class UpdateAbort(Abort):
73 73 """Raised when an update is aborted for destination issue"""
74 74
75 75 class ResponseExpected(Abort):
76 76 """Raised when an EOF is received for a prompt"""
77 77 def __init__(self):
78 78 from .i18n import _
79 79 Abort.__init__(self, _('response expected'))
80 80
81 81 class OutOfBandError(HintException):
82 82 """Exception raised when a remote repo reports failure"""
83 83
84 84 class ParseError(HintException):
85 85 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
86 86
87 87 class UnknownIdentifier(ParseError):
88 88 """Exception raised when a {rev,file}set references an unknown identifier"""
89 89
90 90 def __init__(self, function, symbols):
91 91 from .i18n import _
92 92 ParseError.__init__(self, _("unknown identifier: %s") % function)
93 93 self.function = function
94 94 self.symbols = symbols
95 95
96 96 class RepoError(HintException):
97 97 pass
98 98
99 99 class RepoLookupError(RepoError):
100 100 pass
101 101
102 102 class FilteredRepoLookupError(RepoLookupError):
103 103 pass
104 104
105 105 class CapabilityError(RepoError):
106 106 pass
107 107
108 108 class RequirementError(RepoError):
109 109 """Exception raised if .hg/requires has an unknown entry."""
110 110
111 111 class UnsupportedMergeRecords(Abort):
112 112 def __init__(self, recordtypes):
113 113 from .i18n import _
114 114 self.recordtypes = sorted(recordtypes)
115 115 s = ' '.join(self.recordtypes)
116 116 Abort.__init__(
117 117 self, _('unsupported merge state records: %s') % s,
118 118 hint=_('see https://mercurial-scm.org/wiki/MergeStateRecords for '
119 119 'more information'))
120 120
121 121 class LockError(IOError):
122 122 def __init__(self, errno, strerror, filename, desc):
123 123 IOError.__init__(self, errno, strerror, filename)
124 124 self.desc = desc
125 125
126 126 class LockHeld(LockError):
127 127 def __init__(self, errno, filename, desc, locker):
128 128 LockError.__init__(self, errno, 'Lock held', filename, desc)
129 129 self.locker = locker
130 130
131 131 class LockUnavailable(LockError):
132 132 pass
133 133
134 134 # LockError is for errors while acquiring the lock -- this is unrelated
135 135 class LockInheritanceContractViolation(RuntimeError):
136 136 pass
137 137
138 138 class ResponseError(Exception):
139 139 """Raised to print an error with part of output and exit."""
140 140
141 141 class UnknownCommand(Exception):
142 142 """Exception raised if command is not in the command table."""
143 143
144 144 class AmbiguousCommand(Exception):
145 145 """Exception raised if command shortcut matches more than one command."""
146 146
147 147 # derived from KeyboardInterrupt to simplify some breakout code
148 148 class SignalInterrupt(KeyboardInterrupt):
149 149 """Exception raised on SIGTERM and SIGHUP."""
150 150
151 151 class SignatureError(Exception):
152 152 pass
153 153
154 154 class PushRaced(RuntimeError):
155 155 """An exception raised during unbundling that indicate a push race"""
156 156
157 157 # bundle2 related errors
158 158 class BundleValueError(ValueError):
159 159 """error raised when bundle2 cannot be processed"""
160 160
161 161 class BundleUnknownFeatureError(BundleValueError):
162 162 def __init__(self, parttype=None, params=(), values=()):
163 163 self.parttype = parttype
164 164 self.params = params
165 165 self.values = values
166 166 if self.parttype is None:
167 167 msg = 'Stream Parameter'
168 168 else:
169 169 msg = parttype
170 170 entries = self.params
171 171 if self.params and self.values:
172 172 assert len(self.params) == len(self.values)
173 173 entries = []
174 174 for idx, par in enumerate(self.params):
175 175 val = self.values[idx]
176 176 if val is None:
177 177 entries.append(val)
178 178 else:
179 179 entries.append("%s=%r" % (par, val))
180 180 if entries:
181 181 msg = '%s - %s' % (msg, ', '.join(entries))
182 182 ValueError.__init__(self, msg)
183 183
184 184 class ReadOnlyPartError(RuntimeError):
185 185 """error raised when code tries to alter a part being generated"""
186 186
187 187 class PushkeyFailed(Abort):
188 188 """error raised when a pushkey part failed to update a value"""
189 189
190 190 def __init__(self, partid, namespace=None, key=None, new=None, old=None,
191 191 ret=None):
192 192 self.partid = partid
193 193 self.namespace = namespace
194 194 self.key = key
195 195 self.new = new
196 196 self.old = old
197 197 self.ret = ret
198 198 # no i18n expected to be processed into a better message
199 199 Abort.__init__(self, 'failed to update value for "%s/%s"'
200 200 % (namespace, key))
201 201
202 202 class CensoredNodeError(RevlogError):
203 203 """error raised when content verification fails on a censored node
204 204
205 205 Also contains the tombstone data substituted for the uncensored data.
206 206 """
207 207
208 208 def __init__(self, filename, node, tombstone):
209 209 from .node import short
210 210 RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
211 211 self.tombstone = tombstone
212 212
213 213 class CensoredBaseError(RevlogError):
214 214 """error raised when a delta is rejected because its base is censored
215 215
216 216 A delta based on a censored revision must be formed as single patch
217 217 operation which replaces the entire base with new content. This ensures
218 218 the delta may be applied by clones which have not censored the base.
219 219 """
220 220
221 221 class InvalidBundleSpecification(Exception):
222 222 """error raised when a bundle specification is invalid.
223 223
224 224 This is used for syntax errors as opposed to support errors.
225 225 """
226 226
227 227 class UnsupportedBundleSpecification(Exception):
228 228 """error raised when a bundle specification is not supported."""
General Comments 0
You need to be logged in to leave comments. Login now