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