comparison mercurial/cmdutil.py @ 45846:8d72e29ad1e0

errors: introduce InputError and use it from commands and cmdutil This patch introduces a `InputError` class and replaces many uses of `error.Abort` by it in `commands` and `cmdutil`. This is a part of https://www.mercurial-scm.org/wiki/ErrorCategoriesPlan. There will later be a different class for state errors (to raise e.g. when there's an unfinished operation). It's not always clear when one should report an input error and when it should be a state error. We can always adjust later if I got something wrong in this patch (but feel free to point out any you notice now). Differential Revision: https://phab.mercurial-scm.org/D9167
author Martin von Zweigbergk <martinvonz@google.com>
date Tue, 06 Oct 2020 22:36:15 -0700
parents 976b26bdd0d8
children 527ce85c2e60
comparison
equal deleted inserted replaced
45845:21733e8c924f 45846:8d72e29ad1e0
277 277
278 previous = None 278 previous = None
279 for x in args: 279 for x in args:
280 if opts.get(x): 280 if opts.get(x):
281 if previous: 281 if previous:
282 raise error.Abort( 282 raise error.InputError(
283 _(b'cannot specify both --%s and --%s') 283 _(b'cannot specify both --%s and --%s')
284 % (to_display(previous), to_display(x)) 284 % (to_display(previous), to_display(x))
285 ) 285 )
286 previous = x 286 previous = x
287 return previous 287 return previous
330 note = opts.get(b'note') 330 note = opts.get(b'note')
331 if not note: 331 if not note:
332 return 332 return
333 333
334 if len(note) > 255: 334 if len(note) > 255:
335 raise error.Abort(_(b"cannot store a note of more than 255 bytes")) 335 raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
336 if b'\n' in note: 336 if b'\n' in note:
337 raise error.Abort(_(b"note cannot contain a newline")) 337 raise error.InputError(_(b"note cannot contain a newline"))
338 338
339 339
340 def ishunk(x): 340 def ishunk(x):
341 hunkclasses = (crecordmod.uihunk, patch.recordhunk) 341 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
342 return isinstance(x, hunkclasses) 342 return isinstance(x, hunkclasses)
424 if not ui.interactive(): 424 if not ui.interactive():
425 if cmdsuggest: 425 if cmdsuggest:
426 msg = _(b'running non-interactively, use %s instead') % cmdsuggest 426 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
427 else: 427 else:
428 msg = _(b'running non-interactively') 428 msg = _(b'running non-interactively')
429 raise error.Abort(msg) 429 raise error.InputError(msg)
430 430
431 # make sure username is set before going interactive 431 # make sure username is set before going interactive
432 if not opts.get(b'user'): 432 if not opts.get(b'user'):
433 ui.username() # raise exception, username not provided 433 ui.username() # raise exception, username not provided
434 434
449 if not opts.get(b'interactive-unshelve'): 449 if not opts.get(b'interactive-unshelve'):
450 checkunfinished(repo, commit=True) 450 checkunfinished(repo, commit=True)
451 wctx = repo[None] 451 wctx = repo[None]
452 merge = len(wctx.parents()) > 1 452 merge = len(wctx.parents()) > 1
453 if merge: 453 if merge:
454 raise error.Abort( 454 raise error.InputError(
455 _( 455 _(
456 b'cannot partially commit a merge ' 456 b'cannot partially commit a merge '
457 b'(use "hg commit" instead)' 457 b'(use "hg commit" instead)'
458 ) 458 )
459 ) 459 )
508 508
509 # 1. filter patch, since we are intending to apply subset of it 509 # 1. filter patch, since we are intending to apply subset of it
510 try: 510 try:
511 chunks, newopts = filterfn(ui, originalchunks, match) 511 chunks, newopts = filterfn(ui, originalchunks, match)
512 except error.PatchError as err: 512 except error.PatchError as err:
513 raise error.Abort(_(b'error parsing patch: %s') % err) 513 raise error.InputError(_(b'error parsing patch: %s') % err)
514 opts.update(newopts) 514 opts.update(newopts)
515 515
516 # We need to keep a backup of files that have been newly added and 516 # We need to keep a backup of files that have been newly added and
517 # modified during the recording process because there is a previous 517 # modified during the recording process because there is a previous
518 # version without the edit in the workdir. We also will need to restore 518 # version without the edit in the workdir. We also will need to restore
598 try: 598 try:
599 ui.debug(b'applying patch\n') 599 ui.debug(b'applying patch\n')
600 ui.debug(fp.getvalue()) 600 ui.debug(fp.getvalue())
601 patch.internalpatch(ui, repo, fp, 1, eolmode=None) 601 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
602 except error.PatchError as err: 602 except error.PatchError as err:
603 raise error.Abort(pycompat.bytestr(err)) 603 raise error.InputError(pycompat.bytestr(err))
604 del fp 604 del fp
605 605
606 # 4. We prepared working directory according to filtered 606 # 4. We prepared working directory according to filtered
607 # patch. Now is the time to delegate the job to 607 # patch. Now is the time to delegate the job to
608 # commit/qrefresh or the like! 608 # commit/qrefresh or the like!
760 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c') 760 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
761 761
762 # checking the argument validity 762 # checking the argument validity
763 for s in pycompat.bytestr(terseargs): 763 for s in pycompat.bytestr(terseargs):
764 if s not in allst: 764 if s not in allst:
765 raise error.Abort(_(b"'%s' not recognized") % s) 765 raise error.InputError(_(b"'%s' not recognized") % s)
766 766
767 # creating a dirnode object for the root of the repo 767 # creating a dirnode object for the root of the repo
768 rootobj = dirnode(b'') 768 rootobj = dirnode(b'')
769 pstatus = ( 769 pstatus = (
770 b'modified', 770 b'modified',
966 with repo.wlock(), repo.lock(), repo.transaction(b'branches'): 966 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
967 # abort in case of uncommitted merge or dirty wdir 967 # abort in case of uncommitted merge or dirty wdir
968 bailifchanged(repo) 968 bailifchanged(repo)
969 revs = scmutil.revrange(repo, revs) 969 revs = scmutil.revrange(repo, revs)
970 if not revs: 970 if not revs:
971 raise error.Abort(b"empty revision set") 971 raise error.InputError(b"empty revision set")
972 roots = repo.revs(b'roots(%ld)', revs) 972 roots = repo.revs(b'roots(%ld)', revs)
973 if len(roots) > 1: 973 if len(roots) > 1:
974 raise error.Abort( 974 raise error.InputError(
975 _(b"cannot change branch of non-linear revisions") 975 _(b"cannot change branch of non-linear revisions")
976 ) 976 )
977 rewriteutil.precheck(repo, revs, b'change branch of') 977 rewriteutil.precheck(repo, revs, b'change branch of')
978 978
979 root = repo[roots.first()] 979 root = repo[roots.first()]
981 if ( 981 if (
982 not opts.get(b'force') 982 not opts.get(b'force')
983 and label not in rpb 983 and label not in rpb
984 and label in repo.branchmap() 984 and label in repo.branchmap()
985 ): 985 ):
986 raise error.Abort(_(b"a branch of the same name already exists")) 986 raise error.InputError(
987 _(b"a branch of the same name already exists")
988 )
987 989
988 if repo.revs(b'obsolete() and %ld', revs): 990 if repo.revs(b'obsolete() and %ld', revs):
989 raise error.Abort( 991 raise error.InputError(
990 _(b"cannot change branch of a obsolete changeset") 992 _(b"cannot change branch of a obsolete changeset")
991 ) 993 )
992 994
993 # make sure only topological heads 995 # make sure only topological heads
994 if repo.revs(b'heads(%ld) - head()', revs): 996 if repo.revs(b'heads(%ld) - head()', revs):
995 raise error.Abort(_(b"cannot change branch in middle of a stack")) 997 raise error.InputError(
998 _(b"cannot change branch in middle of a stack")
999 )
996 1000
997 replacements = {} 1001 replacements = {}
998 # avoid import cycle mercurial.cmdutil -> mercurial.context -> 1002 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
999 # mercurial.subrepo -> mercurial.cmdutil 1003 # mercurial.subrepo -> mercurial.cmdutil
1000 from . import context 1004 from . import context
1371 msg = _( 1375 msg = _(
1372 b'cannot specify --changelog or --manifest or --dir ' 1376 b'cannot specify --changelog or --manifest or --dir '
1373 b'without a repository' 1377 b'without a repository'
1374 ) 1378 )
1375 if msg: 1379 if msg:
1376 raise error.Abort(msg) 1380 raise error.InputError(msg)
1377 1381
1378 r = None 1382 r = None
1379 if repo: 1383 if repo:
1380 if cl: 1384 if cl:
1381 r = repo.unfiltered().changelog 1385 r = repo.unfiltered().changelog
1382 elif dir: 1386 elif dir:
1383 if not scmutil.istreemanifest(repo): 1387 if not scmutil.istreemanifest(repo):
1384 raise error.Abort( 1388 raise error.InputError(
1385 _( 1389 _(
1386 b"--dir can only be used on repos with " 1390 b"--dir can only be used on repos with "
1387 b"treemanifest enabled" 1391 b"treemanifest enabled"
1388 ) 1392 )
1389 ) 1393 )
1405 if isinstance(r, revlog.revlog): 1409 if isinstance(r, revlog.revlog):
1406 pass 1410 pass
1407 elif util.safehasattr(r, b'_revlog'): 1411 elif util.safehasattr(r, b'_revlog'):
1408 r = r._revlog # pytype: disable=attribute-error 1412 r = r._revlog # pytype: disable=attribute-error
1409 elif r is not None: 1413 elif r is not None:
1410 raise error.Abort(_(b'%r does not appear to be a revlog') % r) 1414 raise error.InputError(
1415 _(b'%r does not appear to be a revlog') % r
1416 )
1411 1417
1412 if not r: 1418 if not r:
1413 if not returnrevlog: 1419 if not returnrevlog:
1414 raise error.Abort(_(b'cannot give path to non-revlog')) 1420 raise error.InputError(_(b'cannot give path to non-revlog'))
1415 1421
1416 if not file_: 1422 if not file_:
1417 raise error.CommandError(cmd, _(b'invalid arguments')) 1423 raise error.CommandError(cmd, _(b'invalid arguments'))
1418 if not os.path.isfile(file_): 1424 if not os.path.isfile(file_):
1419 raise error.Abort(_(b"revlog '%s' not found") % file_) 1425 raise error.InputError(_(b"revlog '%s' not found") % file_)
1420 r = revlog.revlog( 1426 r = revlog.revlog(
1421 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i" 1427 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1422 ) 1428 )
1423 return r 1429 return r
1424 1430
1451 rev = opts.get(b'at_rev') 1457 rev = opts.get(b'at_rev')
1452 if rev: 1458 if rev:
1453 if not forget and not after: 1459 if not forget and not after:
1454 # TODO: Remove this restriction and make it also create the copy 1460 # TODO: Remove this restriction and make it also create the copy
1455 # targets (and remove the rename source if rename==True). 1461 # targets (and remove the rename source if rename==True).
1456 raise error.Abort(_(b'--at-rev requires --after')) 1462 raise error.InputError(_(b'--at-rev requires --after'))
1457 ctx = scmutil.revsingle(repo, rev) 1463 ctx = scmutil.revsingle(repo, rev)
1458 if len(ctx.parents()) > 1: 1464 if len(ctx.parents()) > 1:
1459 raise error.Abort(_(b'cannot mark/unmark copy in merge commit')) 1465 raise error.InputError(
1466 _(b'cannot mark/unmark copy in merge commit')
1467 )
1460 else: 1468 else:
1461 ctx = repo[None] 1469 ctx = repo[None]
1462 1470
1463 pctx = ctx.p1() 1471 pctx = ctx.p1()
1464 1472
1467 if forget: 1475 if forget:
1468 if ctx.rev() is None: 1476 if ctx.rev() is None:
1469 new_ctx = ctx 1477 new_ctx = ctx
1470 else: 1478 else:
1471 if len(ctx.parents()) > 1: 1479 if len(ctx.parents()) > 1:
1472 raise error.Abort(_(b'cannot unmark copy in merge commit')) 1480 raise error.InputError(_(b'cannot unmark copy in merge commit'))
1473 # avoid cycle context -> subrepo -> cmdutil 1481 # avoid cycle context -> subrepo -> cmdutil
1474 from . import context 1482 from . import context
1475 1483
1476 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy') 1484 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1477 new_ctx = context.overlayworkingctx(repo) 1485 new_ctx = context.overlayworkingctx(repo)
1510 1518
1511 return 1519 return
1512 1520
1513 pats = scmutil.expandpats(pats) 1521 pats = scmutil.expandpats(pats)
1514 if not pats: 1522 if not pats:
1515 raise error.Abort(_(b'no source or destination specified')) 1523 raise error.InputError(_(b'no source or destination specified'))
1516 if len(pats) == 1: 1524 if len(pats) == 1:
1517 raise error.Abort(_(b'no destination specified')) 1525 raise error.InputError(_(b'no destination specified'))
1518 dest = pats.pop() 1526 dest = pats.pop()
1519 1527
1520 def walkpat(pat): 1528 def walkpat(pat):
1521 srcs = [] 1529 srcs = []
1522 # TODO: Inline and simplify the non-working-copy version of this code 1530 # TODO: Inline and simplify the non-working-copy version of this code
1552 1560
1553 if ctx.rev() is not None: 1561 if ctx.rev() is not None:
1554 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy') 1562 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1555 absdest = pathutil.canonpath(repo.root, cwd, dest) 1563 absdest = pathutil.canonpath(repo.root, cwd, dest)
1556 if ctx.hasdir(absdest): 1564 if ctx.hasdir(absdest):
1557 raise error.Abort( 1565 raise error.InputError(
1558 _(b'%s: --at-rev does not support a directory as destination') 1566 _(b'%s: --at-rev does not support a directory as destination')
1559 % uipathfn(absdest) 1567 % uipathfn(absdest)
1560 ) 1568 )
1561 if absdest not in ctx: 1569 if absdest not in ctx:
1562 raise error.Abort( 1570 raise error.InputError(
1563 _(b'%s: copy destination does not exist in %s') 1571 _(b'%s: copy destination does not exist in %s')
1564 % (uipathfn(absdest), ctx) 1572 % (uipathfn(absdest), ctx)
1565 ) 1573 )
1566 1574
1567 # avoid cycle context -> subrepo -> cmdutil 1575 # avoid cycle context -> subrepo -> cmdutil
1574 continue 1582 continue
1575 for abs, rel, exact in srcs: 1583 for abs, rel, exact in srcs:
1576 copylist.append(abs) 1584 copylist.append(abs)
1577 1585
1578 if not copylist: 1586 if not copylist:
1579 raise error.Abort(_(b'no files to copy')) 1587 raise error.InputError(_(b'no files to copy'))
1580 # TODO: Add support for `hg cp --at-rev . foo bar dir` and 1588 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1581 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the 1589 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1582 # existing functions below. 1590 # existing functions below.
1583 if len(copylist) != 1: 1591 if len(copylist) != 1:
1584 raise error.Abort(_(b'--at-rev requires a single source')) 1592 raise error.InputError(_(b'--at-rev requires a single source'))
1585 1593
1586 new_ctx = context.overlayworkingctx(repo) 1594 new_ctx = context.overlayworkingctx(repo)
1587 new_ctx.setbase(ctx.p1()) 1595 new_ctx.setbase(ctx.p1())
1588 mergemod.graft(repo, ctx, wctx=new_ctx) 1596 mergemod.graft(repo, ctx, wctx=new_ctx)
1589 1597
1807 return res 1815 return res
1808 1816
1809 destdirexists = os.path.isdir(dest) and not os.path.islink(dest) 1817 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1810 if not destdirexists: 1818 if not destdirexists:
1811 if len(pats) > 1 or matchmod.patkind(pats[0]): 1819 if len(pats) > 1 or matchmod.patkind(pats[0]):
1812 raise error.Abort( 1820 raise error.InputError(
1813 _( 1821 _(
1814 b'with multiple sources, destination must be an ' 1822 b'with multiple sources, destination must be an '
1815 b'existing directory' 1823 b'existing directory'
1816 ) 1824 )
1817 ) 1825 )
1818 if util.endswithsep(dest): 1826 if util.endswithsep(dest):
1819 raise error.Abort(_(b'destination %s is not a directory') % dest) 1827 raise error.InputError(
1828 _(b'destination %s is not a directory') % dest
1829 )
1820 1830
1821 tfn = targetpathfn 1831 tfn = targetpathfn
1822 if after: 1832 if after:
1823 tfn = targetpathafterfn 1833 tfn = targetpathafterfn
1824 copylist = [] 1834 copylist = []
1826 srcs = walkpat(pat) 1836 srcs = walkpat(pat)
1827 if not srcs: 1837 if not srcs:
1828 continue 1838 continue
1829 copylist.append((tfn(pat, dest, srcs), srcs)) 1839 copylist.append((tfn(pat, dest, srcs), srcs))
1830 if not copylist: 1840 if not copylist:
1831 raise error.Abort(_(b'no files to copy')) 1841 raise error.InputError(_(b'no files to copy'))
1832 1842
1833 errors = 0 1843 errors = 0
1834 for targetpath, srcs in copylist: 1844 for targetpath, srcs in copylist:
1835 for abssrc, relsrc, exact in srcs: 1845 for abssrc, relsrc, exact in srcs:
1836 if copyfile(abssrc, relsrc, targetpath(abssrc), exact): 1846 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1917 1927
1918 if len(parents) == 1: 1928 if len(parents) == 1:
1919 parents.append(repo[nullid]) 1929 parents.append(repo[nullid])
1920 if opts.get(b'exact'): 1930 if opts.get(b'exact'):
1921 if not nodeid or not p1: 1931 if not nodeid or not p1:
1922 raise error.Abort(_(b'not a Mercurial patch')) 1932 raise error.InputError(_(b'not a Mercurial patch'))
1923 p1 = repo[p1] 1933 p1 = repo[p1]
1924 p2 = repo[p2 or nullid] 1934 p2 = repo[p2 or nullid]
1925 elif p2: 1935 elif p2:
1926 try: 1936 try:
1927 p1 = repo[p1] 1937 p1 = repo[p1]
2253 """Find the tipmost changeset that matches the given date spec""" 2263 """Find the tipmost changeset that matches the given date spec"""
2254 mrevs = repo.revs(b'date(%s)', date) 2264 mrevs = repo.revs(b'date(%s)', date)
2255 try: 2265 try:
2256 rev = mrevs.max() 2266 rev = mrevs.max()
2257 except ValueError: 2267 except ValueError:
2258 raise error.Abort(_(b"revision matching date not found")) 2268 raise error.InputError(_(b"revision matching date not found"))
2259 2269
2260 ui.status( 2270 ui.status(
2261 _(b"found revision %d from %s\n") 2271 _(b"found revision %d from %s\n")
2262 % (rev, dateutil.datestr(repo[rev].date())) 2272 % (rev, dateutil.datestr(repo[rev].date()))
2263 ) 2273 )
2336 2346
2337 def forget( 2347 def forget(
2338 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive 2348 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2339 ): 2349 ):
2340 if dryrun and interactive: 2350 if dryrun and interactive:
2341 raise error.Abort(_(b"cannot specify both --dry-run and --interactive")) 2351 raise error.InputError(
2352 _(b"cannot specify both --dry-run and --interactive")
2353 )
2342 bad = [] 2354 bad = []
2343 badfn = lambda x, y: bad.append(x) or match.bad(x, y) 2355 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2344 wctx = repo[None] 2356 wctx = repo[None]
2345 forgot = [] 2357 forgot = []
2346 2358
3048 os.chdir(olddir) 3060 os.chdir(olddir)
3049 3061
3050 if finishdesc: 3062 if finishdesc:
3051 text = finishdesc(text) 3063 text = finishdesc(text)
3052 if not text.strip(): 3064 if not text.strip():
3053 raise error.Abort(_(b"empty commit message")) 3065 raise error.InputError(_(b"empty commit message"))
3054 if unchangedmessagedetection and editortext == templatetext: 3066 if unchangedmessagedetection and editortext == templatetext:
3055 raise error.Abort(_(b"commit message unchanged")) 3067 raise error.InputError(_(b"commit message unchanged"))
3056 3068
3057 return text 3069 return text
3058 3070
3059 3071
3060 def buildcommittemplate(repo, ctx, subs, extramsg, ref): 3072 def buildcommittemplate(repo, ctx, subs, extramsg, ref):