Commit 3fe37679 authored by Martin von Zweigbergk's avatar Martin von Zweigbergk

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
parent 21733e8c924f
......@@ -279,7 +279,7 @@ def check_at_most_one_arg(opts, *args):
for x in args:
if opts.get(x):
if previous:
raise error.Abort(
raise error.InputError(
_(b'cannot specify both --%s and --%s')
% (to_display(previous), to_display(x))
)
......@@ -332,9 +332,9 @@ def checknotesize(ui, opts):
return
if len(note) > 255:
raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
raise error.InputError(_(b"cannot store a note of more than 255 bytes"))
if b'\n' in note:
raise error.Abort(_(b"note cannot contain a newline"))
raise error.InputError(_(b"note cannot contain a newline"))
def ishunk(x):
......@@ -426,7 +426,7 @@ def dorecord(
msg = _(b'running non-interactively, use %s instead') % cmdsuggest
else:
msg = _(b'running non-interactively')
raise error.Abort(msg)
raise error.InputError(msg)
# make sure username is set before going interactive
if not opts.get(b'user'):
......@@ -451,7 +451,7 @@ def dorecord(
wctx = repo[None]
merge = len(wctx.parents()) > 1
if merge:
raise error.Abort(
raise error.InputError(
_(
b'cannot partially commit a merge '
b'(use "hg commit" instead)'
......@@ -510,7 +510,7 @@ def dorecord(
try:
chunks, newopts = filterfn(ui, originalchunks, match)
except error.PatchError as err:
raise error.Abort(_(b'error parsing patch: %s') % err)
raise error.InputError(_(b'error parsing patch: %s') % err)
opts.update(newopts)
# We need to keep a backup of files that have been newly added and
......@@ -600,7 +600,7 @@ def dorecord(
ui.debug(fp.getvalue())
patch.internalpatch(ui, repo, fp, 1, eolmode=None)
except error.PatchError as err:
raise error.Abort(pycompat.bytestr(err))
raise error.InputError(pycompat.bytestr(err))
del fp
# 4. We prepared working directory according to filtered
......@@ -762,7 +762,7 @@ def tersedir(statuslist, terseargs):
# checking the argument validity
for s in pycompat.bytestr(terseargs):
if s not in allst:
raise error.Abort(_(b"'%s' not recognized") % s)
raise error.InputError(_(b"'%s' not recognized") % s)
# creating a dirnode object for the root of the repo
rootobj = dirnode(b'')
......@@ -968,10 +968,10 @@ def changebranch(ui, repo, revs, label, opts):
bailifchanged(repo)
revs = scmutil.revrange(repo, revs)
if not revs:
raise error.Abort(b"empty revision set")
raise error.InputError(b"empty revision set")
roots = repo.revs(b'roots(%ld)', revs)
if len(roots) > 1:
raise error.Abort(
raise error.InputError(
_(b"cannot change branch of non-linear revisions")
)
rewriteutil.precheck(repo, revs, b'change branch of')
......@@ -983,16 +983,20 @@ def changebranch(ui, repo, revs, label, opts):
and label not in rpb
and label in repo.branchmap()
):
raise error.Abort(_(b"a branch of the same name already exists"))
raise error.InputError(
_(b"a branch of the same name already exists")
)
if repo.revs(b'obsolete() and %ld', revs):
raise error.Abort(
raise error.InputError(
_(b"cannot change branch of a obsolete changeset")
)
# make sure only topological heads
if repo.revs(b'heads(%ld) - head()', revs):
raise error.Abort(_(b"cannot change branch in middle of a stack"))
raise error.InputError(
_(b"cannot change branch in middle of a stack")
)
replacements = {}
# avoid import cycle mercurial.cmdutil -> mercurial.context ->
......@@ -1373,7 +1377,7 @@ def openstorage(repo, cmd, file_, opts, returnrevlog=False):
b'without a repository'
)
if msg:
raise error.Abort(msg)
raise error.InputError(msg)
r = None
if repo:
......@@ -1381,7 +1385,7 @@ def openstorage(repo, cmd, file_, opts, returnrevlog=False):
r = repo.unfiltered().changelog
elif dir:
if not scmutil.istreemanifest(repo):
raise error.Abort(
raise error.InputError(
_(
b"--dir can only be used on repos with "
b"treemanifest enabled"
......@@ -1407,16 +1411,18 @@ def openstorage(repo, cmd, file_, opts, returnrevlog=False):
elif util.safehasattr(r, b'_revlog'):
r = r._revlog # pytype: disable=attribute-error
elif r is not None:
raise error.Abort(_(b'%r does not appear to be a revlog') % r)
raise error.InputError(
_(b'%r does not appear to be a revlog') % r
)
if not r:
if not returnrevlog:
raise error.Abort(_(b'cannot give path to non-revlog'))
raise error.InputError(_(b'cannot give path to non-revlog'))
if not file_:
raise error.CommandError(cmd, _(b'invalid arguments'))
if not os.path.isfile(file_):
raise error.Abort(_(b"revlog '%s' not found") % file_)
raise error.InputError(_(b"revlog '%s' not found") % file_)
r = revlog.revlog(
vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
)
......@@ -1453,10 +1459,12 @@ def copy(ui, repo, pats, opts, rename=False):
if not forget and not after:
# TODO: Remove this restriction and make it also create the copy
# targets (and remove the rename source if rename==True).
raise error.Abort(_(b'--at-rev requires --after'))
raise error.InputError(_(b'--at-rev requires --after'))
ctx = scmutil.revsingle(repo, rev)
if len(ctx.parents()) > 1:
raise error.Abort(_(b'cannot mark/unmark copy in merge commit'))
raise error.InputError(
_(b'cannot mark/unmark copy in merge commit')
)
else:
ctx = repo[None]
......@@ -1469,7 +1477,7 @@ def copy(ui, repo, pats, opts, rename=False):
new_ctx = ctx
else:
if len(ctx.parents()) > 1:
raise error.Abort(_(b'cannot unmark copy in merge commit'))
raise error.InputError(_(b'cannot unmark copy in merge commit'))
# avoid cycle context -> subrepo -> cmdutil
from . import context
......@@ -1512,9 +1520,9 @@ def copy(ui, repo, pats, opts, rename=False):
pats = scmutil.expandpats(pats)
if not pats:
raise error.Abort(_(b'no source or destination specified'))
raise error.InputError(_(b'no source or destination specified'))
if len(pats) == 1:
raise error.Abort(_(b'no destination specified'))
raise error.InputError(_(b'no destination specified'))
dest = pats.pop()
def walkpat(pat):
......@@ -1554,12 +1562,12 @@ def copy(ui, repo, pats, opts, rename=False):
rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
absdest = pathutil.canonpath(repo.root, cwd, dest)
if ctx.hasdir(absdest):
raise error.Abort(
raise error.InputError(
_(b'%s: --at-rev does not support a directory as destination')
% uipathfn(absdest)
)
if absdest not in ctx:
raise error.Abort(
raise error.InputError(
_(b'%s: copy destination does not exist in %s')
% (uipathfn(absdest), ctx)
)
......@@ -1576,12 +1584,12 @@ def copy(ui, repo, pats, opts, rename=False):
copylist.append(abs)
if not copylist:
raise error.Abort(_(b'no files to copy'))
raise error.InputError(_(b'no files to copy'))
# TODO: Add support for `hg cp --at-rev . foo bar dir` and
# `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
# existing functions below.
if len(copylist) != 1:
raise error.Abort(_(b'--at-rev requires a single source'))
raise error.InputError(_(b'--at-rev requires a single source'))
new_ctx = context.overlayworkingctx(repo)
new_ctx.setbase(ctx.p1())
......@@ -1809,14 +1817,16 @@ def copy(ui, repo, pats, opts, rename=False):
destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
if not destdirexists:
if len(pats) > 1 or matchmod.patkind(pats[0]):
raise error.Abort(
raise error.InputError(
_(
b'with multiple sources, destination must be an '
b'existing directory'
)
)
if util.endswithsep(dest):
raise error.Abort(_(b'destination %s is not a directory') % dest)
raise error.InputError(
_(b'destination %s is not a directory') % dest
)
tfn = targetpathfn
if after:
......@@ -1828,7 +1838,7 @@ def copy(ui, repo, pats, opts, rename=False):
continue
copylist.append((tfn(pat, dest, srcs), srcs))
if not copylist:
raise error.Abort(_(b'no files to copy'))
raise error.InputError(_(b'no files to copy'))
errors = 0
for targetpath, srcs in copylist:
......@@ -1919,7 +1929,7 @@ def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
parents.append(repo[nullid])
if opts.get(b'exact'):
if not nodeid or not p1:
raise error.Abort(_(b'not a Mercurial patch'))
raise error.InputError(_(b'not a Mercurial patch'))
p1 = repo[p1]
p2 = repo[p2 or nullid]
elif p2:
......@@ -2255,7 +2265,7 @@ def finddate(ui, repo, date):
try:
rev = mrevs.max()
except ValueError:
raise error.Abort(_(b"revision matching date not found"))
raise error.InputError(_(b"revision matching date not found"))
ui.status(
_(b"found revision %d from %s\n")
......@@ -2338,7 +2348,9 @@ def forget(
ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
):
if dryrun and interactive:
raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
raise error.InputError(
_(b"cannot specify both --dry-run and --interactive")
)
bad = []
badfn = lambda x, y: bad.append(x) or match.bad(x, y)
wctx = repo[None]
......@@ -3050,9 +3062,9 @@ def commitforceeditor(
if finishdesc:
text = finishdesc(text)
if not text.strip():
raise error.Abort(_(b"empty commit message"))
raise error.InputError(_(b"empty commit message"))
if unchangedmessagedetection and editortext == templatetext:
raise error.Abort(_(b"commit message unchanged"))
raise error.InputError(_(b"commit message unchanged"))
return text
......
This diff is collapsed.
......@@ -181,6 +181,13 @@ class Abort(Hint, Exception):
return pycompat.sysstr(self.__bytes__())
class InputError(Abort):
"""Indicates that the user made an error in their input.
Examples: Invalid command, invalid flags, invalid revision.
"""
class HookLoadError(Abort):
"""raised when loading a hook fails, aborting an operation
......
......@@ -219,6 +219,8 @@ def callcatch(ui, func):
except error.WdirUnsupported:
ui.error(_(b"abort: working directory revision cannot be specified\n"))
except error.Abort as inst:
if isinstance(inst, error.InputError):
detailed_exit_code = 10
ui.error(_(b"abort: %s\n") % inst.message)
if inst.hint:
ui.error(_(b"(%s)\n") % inst.hint)
......
......@@ -286,7 +286,7 @@ test --interactive mode in forget
$ hg commit -qAm "bar"
$ hg forget foo --dry-run -i
abort: cannot specify both --dry-run and --interactive
[255]
[10]
$ hg forget foo --config ui.interactive=True -i << EOF
> ?
......
......@@ -153,7 +153,7 @@ Amend with editor
$ echo FOO > $TESTTMP/msg
$ hg amend -l $TESTTMP/msg -m BAR
abort: cannot specify both --message and --logfile
[255]
[10]
$ hg amend -l $TESTTMP/msg
saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/974f07f28537-edb6470a-amend.hg (obsstore-off !)
$ hg log -r . -T '{node|short} {desc}\n'
......@@ -226,7 +226,7 @@ Checking the note stored in the obsmarker
$ hg add bar
$ hg amend --note 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
abort: cannot store a note of more than 255 bytes
[255]
[10]
$ hg amend --note "adding bar"
$ hg debugobsolete -r .
112478962961147124edd43549aedd1a335e44bf be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
......@@ -471,7 +471,7 @@ Bad combination of date options:
$ hg amend -D --date '0 0'
abort: cannot specify both --date and --currentdate
[255]
[10]
Close branch
......
......@@ -560,7 +560,7 @@ empty repo
$ cd ../empty
$ hg archive ../test-empty
abort: no working directory: please specify a revision
[255]
[10]
old file -- date clamped to 1980
......
......@@ -5,10 +5,10 @@ should complain
$ hg backout
abort: please specify a revision to backout
[255]
[10]
$ hg backout -r 0 0
abort: please specify just one revision
[255]
[10]
basic operation
(this also tests that editor is invoked if the commit message is not
......@@ -210,7 +210,7 @@ should fail
$ hg backout 1
abort: cannot backout change that is not an ancestor
[255]
[10]
$ echo c > c
$ hg ci -Am2
adding c
......@@ -227,7 +227,7 @@ should fail
$ hg backout 1
abort: cannot backout change that is not an ancestor
[255]
[10]
$ hg summary
parent: 2:db815d6d32e6 tip
2
......@@ -464,19 +464,19 @@ backout of merge should fail
$ hg backout 4
abort: cannot backout a merge changeset
[255]
[10]
backout of merge with bad parent should fail
$ hg backout --parent 0 4
abort: cb9a9f314b8b is not a parent of b2f3bb92043e
[255]
[10]
backout of non-merge with parent should fail
$ hg backout --parent 0 3
abort: cannot use --parent on non-merge changeset
[255]
[10]
backout with valid parent should be ok
......@@ -805,7 +805,7 @@ Test usage of `hg resolve` in case of conflict
$ hg backout --merge --no-commit 2
abort: cannot specify both --no-commit and --merge
[255]
[10]
Ensure that backout out the same changeset twice performs correctly:
......
......@@ -751,46 +751,46 @@ Test the validation message when exclusive options are used:
$ hg bisect -r
$ hg bisect -b -c false
abort: --bad and --command are incompatible
[255]
[10]
$ hg bisect -b -e
abort: --bad and --extend are incompatible
[255]
[10]
$ hg bisect -b -g
abort: --bad and --good are incompatible
[255]
[10]
$ hg bisect -b -r
abort: --bad and --reset are incompatible
[255]
[10]
$ hg bisect -b -s
abort: --bad and --skip are incompatible
[255]
[10]
$ hg bisect -c false -e
abort: --command and --extend are incompatible
[255]
[10]
$ hg bisect -c false -g
abort: --command and --good are incompatible
[255]
[10]
$ hg bisect -c false -r
abort: --command and --reset are incompatible
[255]
[10]
$ hg bisect -c false -s
abort: --command and --skip are incompatible
[255]
[10]
$ hg bisect -e -g
abort: --extend and --good are incompatible
[255]
[10]
$ hg bisect -e -r
abort: --extend and --reset are incompatible
[255]
[10]
$ hg bisect -e -s
abort: --extend and --skip are incompatible
[255]
[10]
$ hg bisect -g -r
abort: --good and --reset are incompatible
[255]
[10]
$ hg bisect -g -s
abort: --good and --skip are incompatible
[255]
[10]
$ hg bisect -r -s
abort: --reset and --skip are incompatible
[255]
[10]
......@@ -386,7 +386,7 @@ push/pull name that doesn't exist
$ hg pull -B anotherbadname ../a
pulling from ../a
abort: remote bookmark anotherbadname not found!
[255]
[10]
divergent bookmarks
......@@ -1382,10 +1382,10 @@ Test pushing all bookmarks
[1]
$ hg push --all-bookmarks -r 1 ../ab2
abort: cannot specify both --all-bookmarks and --rev
[255]
[10]
$ hg push --all-bookmarks -B A ../ab2
abort: cannot specify both --all-bookmarks and --bookmark
[255]
[10]
$ hg push --all-bookmarks ../ab2
pushing to ../ab2
searching for changes
......
......@@ -82,10 +82,10 @@ list bookmarks
[255]
$ hg bookmarks -l -r0
abort: cannot specify both --list and --rev
[255]
[10]
$ hg bookmarks -l --inactive
abort: cannot specify both --inactive and --list
[255]
[10]
$ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
0 X
......@@ -327,16 +327,16 @@ rename without new name or multiple names
$ hg bookmark -m Y
abort: new bookmark name required
[255]
[10]
$ hg bookmark -m Y Y2 Y3
abort: only one new bookmark name allowed
[255]
[10]
delete without name
$ hg bookmark -d
abort: bookmark name required
[255]
[10]
delete nonexistent bookmark
......@@ -348,7 +348,7 @@ delete with --inactive
$ hg bookmark -d --inactive Y
abort: cannot specify both --inactive and --delete
[255]
[10]
bookmark name with spaces should be stripped
......@@ -476,15 +476,15 @@ incompatible options
$ hg bookmark -m Y -d Z
abort: cannot specify both --delete and --rename
[255]
[10]
$ hg bookmark -r 1 -d Z
abort: cannot specify both --delete and --rev
[255]
[10]
$ hg bookmark -r 1 -m Z Y
abort: cannot specify both --rename and --rev
[255]
[10]
force bookmark with existing name
......@@ -522,7 +522,7 @@ revision but no bookmark name
$ hg bookmark -r .
abort: bookmark name required
[255]
[10]
bookmark name with whitespace only
......@@ -793,7 +793,7 @@ pull --update works the same as pull && update
$ hg -R ../cloned-bookmarks-manual-update update -d 1986
abort: revision matching date not found
[255]
[10]
$ hg -R ../cloned-bookmarks-manual-update update
updating to active bookmark Y
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
......
......@@ -34,7 +34,7 @@ Try without passing a new branch name
$ hg branch -r .
abort: no branch name specified for the revisions
[255]
[10]
Setting an invalid branch name
......@@ -52,7 +52,7 @@ Change on non-linear set of commits
$ hg branch -r 2 -r 4 foo
abort: cannot change branch of non-linear revisions
[255]
[10]
Change in middle of the stack (linear commits)
......@@ -74,7 +74,7 @@ Change on empty revision set
$ hg branch -r 'draft() - all()' foo
abort: empty revision set
[255]
[10]
Changing branch on linear set of commits from head
......@@ -123,7 +123,7 @@ Change branch name to an existing branch
$ hg branch -r . default
abort: a branch of the same name already exists
[255]
[10]
Changing on a branch head which is not topological head
......@@ -149,7 +149,7 @@ Changing branch of an obsoleted changeset