Mercurial > public > mercurial-scm > hg-stable
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): |