Patchwork D9310: errors: introduce StateError and use it from commands and cmdutil

login
register
mail settings
Submitter phabricator
Date Nov. 12, 2020, 4:44 p.m.
Message ID <differential-rev-PHID-DREV-cr6jfki4nzac4izgm7k3-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/47588/
State Superseded
Headers show

Comments

phabricator - Nov. 12, 2020, 4:44 p.m.
martinvonz created this revision.
Herald added a reviewer: durin42.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  This very similar to an earlier patch (which was for `InputError`).
  
  In this patch, I also updated the transplant extension only because
  `test-transplant.t` would otherwise have needed a `#if continueflag`.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D9310

AFFECTED FILES
  hgext/transplant.py
  mercurial/cmdutil.py
  mercurial/commands.py
  mercurial/error.py
  mercurial/scmutil.py
  tests/test-absorb-unfinished.t
  tests/test-branch-change.t
  tests/test-commit-unresolved.t
  tests/test-fetch.t
  tests/test-fix.t
  tests/test-graft-interrupted.t
  tests/test-graft.t
  tests/test-histedit-arguments.t
  tests/test-histedit-edit.t
  tests/test-histedit-merge-tools.t
  tests/test-histedit-no-change.t
  tests/test-largefiles-misc.t
  tests/test-merge-subrepos.t
  tests/test-merge1.t
  tests/test-merge5.t
  tests/test-mq.t
  tests/test-pathconflicts-update.t
  tests/test-pull-update.t
  tests/test-qrecord.t
  tests/test-rebase-abort.t
  tests/test-rebase-conflicts.t
  tests/test-rebase-inmemory.t
  tests/test-rebase-named-branches.t
  tests/test-rebase-obsolete.t
  tests/test-rebase-parameters.t
  tests/test-rebase-pull.t
  tests/test-resolve.t
  tests/test-shelve.t
  tests/test-shelve2.t
  tests/test-split.t
  tests/test-strip.t
  tests/test-tag.t
  tests/test-transplant.t
  tests/test-uncommit.t
  tests/test-update-branches.t
  tests/test-update-issue1456.t

CHANGE DETAILS




To: martinvonz, durin42, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/tests/test-update-issue1456.t b/tests/test-update-issue1456.t
--- a/tests/test-update-issue1456.t
+++ b/tests/test-update-issue1456.t
@@ -19,7 +19,7 @@ 
   $ echo dirty > foo
   $ hg up -c
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg up -q
   $ cat foo
   dirty
diff --git a/tests/test-update-branches.t b/tests/test-update-branches.t
--- a/tests/test-update-branches.t
+++ b/tests/test-update-branches.t
@@ -701,6 +701,6 @@ 
   > EOF
   $ hg co 2
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg co --no-check 2
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -168,11 +168,11 @@ 
   $ hg uncommit
   abort: uncommitted changes
   (requires --allow-dirty-working-copy to uncommit)
-  [255]
+  [20]
   $ hg uncommit files
   abort: uncommitted changes
   (requires --allow-dirty-working-copy to uncommit)
-  [255]
+  [20]
   $ cat files
   abcde
   foo
@@ -184,7 +184,7 @@ 
   $ hg uncommit
   abort: uncommitted changes
   (requires --allow-dirty-working-copy to uncommit)
-  [255]
+  [20]
   $ hg uncommit --config experimental.uncommitondirtywdir=True
   $ hg commit -m "files abcde + foo"
 
@@ -407,7 +407,7 @@ 
   $ hg uncommit
   abort: outstanding uncommitted merge
   (requires --allow-dirty-working-copy to uncommit)
-  [255]
+  [20]
 
   $ hg uncommit --config experimental.uncommitondirtywdir=True
   abort: cannot uncommit while merging
@@ -507,7 +507,7 @@ 
   $ hg uncommit b
   abort: uncommitted changes
   (requires --allow-dirty-working-copy to uncommit)
-  [255]
+  [20]
   $ hg uncommit --allow-dirty-working-copy b
   $ hg log
   changeset:   3:30fa958635b2
diff --git a/tests/test-transplant.t b/tests/test-transplant.t
--- a/tests/test-transplant.t
+++ b/tests/test-transplant.t
@@ -53,12 +53,12 @@ 
   $ hg transplant 1
   abort: outstanding uncommitted merge
   (use 'hg commit' or 'hg merge --abort')
-  [255]
+  [20]
   $ hg up -qC tip
   $ echo b0 > b1
   $ hg transplant 1
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg up -qC tip
   $ echo b2 > b2
   $ hg ci -Amb2 -d '1 0'
@@ -513,10 +513,10 @@ 
   $ hg continue
   abort: no transplant to continue (continueflag !)
   abort: no operation in progress (no-continueflag !)
-  [255]
+  [20]
   $ hg transplant --stop
   abort: no interrupted transplant found
-  [255]
+  [20]
   $ hg transplant 1
   applying 46ae92138f3c
   patching file foo
@@ -571,7 +571,7 @@ 
   $ hg transplant 1:3
   abort: transplant in progress
   (use 'hg transplant --continue' or 'hg transplant --stop')
-  [255]
+  [20]
   $ hg status -v
   A bar
   ? added.rej
diff --git a/tests/test-tag.t b/tests/test-tag.t
--- a/tests/test-tag.t
+++ b/tests/test-tag.t
@@ -492,11 +492,11 @@ 
 
   $ hg tag t1
   abort: uncommitted merge
-  [255]
+  [20]
   $ hg status
   $ hg tag --rev 1 t2
   abort: uncommitted merge
-  [255]
+  [20]
   $ hg tag --rev 1 --local t3
   $ hg tags -v
   tip                                2:2a156e8887cc
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -317,7 +317,7 @@ 
   $ hg strip 4
   abort: outstanding uncommitted merge
   (use 'hg commit' or 'hg merge --abort')
-  [255]
+  [20]
 ##strip allowed --force with merge in progress
   $ hg strip 4 --force
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -606,7 +606,7 @@ 
   $ echo c > b
   $ hg strip tip
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg strip tip --keep
   saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
   $ hg log --graph
@@ -758,7 +758,7 @@ 
   $ hg add a
   $ hg strip -B B
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg bookmarks
    * B                         6:ff43616e5d0f
 
diff --git a/tests/test-split.t b/tests/test-split.t
--- a/tests/test-split.t
+++ b/tests/test-split.t
@@ -89,7 +89,7 @@ 
   $ hg add dirty
   $ hg split .
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg forget dirty
   $ rm dirty
 
diff --git a/tests/test-shelve2.t b/tests/test-shelve2.t
--- a/tests/test-shelve2.t
+++ b/tests/test-shelve2.t
@@ -737,7 +737,7 @@ 
 #if abortflag
   $ hg unshelve --abort
   abort: no unshelve in progress
-  [255]
+  [20]
 #else
   $ hg abort
   aborting the merge, updating back to 9451eaa6eee3
@@ -912,7 +912,7 @@ 
   $ hg merge --abort
   abort: cannot abort merge with unshelve in progress
   (use 'hg unshelve --continue' or 'hg unshelve --abort')
-  [255]
+  [20]
 
   $ hg unshelve --abort
   unshelve of 'default' aborted
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -442,7 +442,7 @@ 
   $ hg shelve
   abort: unshelve already in progress
   (use 'hg unshelve --continue' or 'hg unshelve --abort')
-  [255]
+  [20]
 
 abort the unshelve and be happy
 
@@ -474,7 +474,7 @@ 
 
   $ hg unshelve -c
   abort: no unshelve in progress
-  [255]
+  [20]
   $ hg status
   A foo/foo
   ? a/a.orig
@@ -501,12 +501,12 @@ 
   $ hg commit -m 'commit while unshelve in progress'
   abort: unshelve already in progress
   (use 'hg unshelve --continue' or 'hg unshelve --abort')
-  [255]
+  [20]
 
   $ hg graft --continue
   abort: no graft in progress
   (continue: hg unshelve --continue)
-  [255]
+  [20]
   $ hg unshelve -c
   unshelve of 'default' complete
 
@@ -1184,7 +1184,7 @@ 
   $ hg unshelve
   abort: outstanding uncommitted merge
   (use 'hg commit' or 'hg merge --abort')
-  [255]
+  [20]
 
   $ cd ..
 
@@ -1483,7 +1483,7 @@ 
 
   $ hg unshelve --continue
   abort: no unshelve in progress
-  [255]
+  [20]
 
   $ hg shelve --list
   default-01      (*)* changes to: add A to bars (glob)
diff --git a/tests/test-resolve.t b/tests/test-resolve.t
--- a/tests/test-resolve.t
+++ b/tests/test-resolve.t
@@ -132,13 +132,13 @@ 
 
   $ hg resolve --all
   abort: resolve command not applicable when not merging
-  [255]
+  [20]
 
 resolve -m should abort when no merge in progress
 
   $ hg resolve -m
   abort: resolve command not applicable when not merging
-  [255]
+  [20]
 
 can not update or merge when there are unresolved conflicts
 
@@ -332,7 +332,7 @@ 
     file2
   abort: conflict markers detected
   (use --all to mark anyway)
-  [255]
+  [20]
   $ hg resolve -l
   U file1
   U file2
diff --git a/tests/test-rebase-pull.t b/tests/test-rebase-pull.t
--- a/tests/test-rebase-pull.t
+++ b/tests/test-rebase-pull.t
@@ -80,7 +80,7 @@ 
   $ hg pull --rebase
   abort: uncommitted changes
   (cannot pull with rebase: please commit or shelve your changes first)
-  [255]
+  [20]
   $ hg update --clean --quiet
 
 Abort pull early if another operation (histedit) is in progress:
@@ -94,7 +94,7 @@ 
   $ hg pull --rebase
   abort: histedit in progress
   (use 'hg histedit --continue' or 'hg histedit --abort')
-  [255]
+  [20]
   $ hg histedit --abort --quiet
 
 Abort pull early with pending uncommitted merge:
@@ -117,7 +117,7 @@ 
   $ hg pull --rebase
   abort: outstanding uncommitted merge
   (cannot pull with rebase: please commit or shelve your changes first)
-  [255]
+  [20]
   $ hg update --clean --quiet
 
 Abort pull early with unclean subrepo:
diff --git a/tests/test-rebase-parameters.t b/tests/test-rebase-parameters.t
--- a/tests/test-rebase-parameters.t
+++ b/tests/test-rebase-parameters.t
@@ -500,7 +500,7 @@ 
   $ hg graft --continue
   abort: no graft in progress
   (continue: hg rebase --continue)
-  [255]
+  [20]
   $ hg rebase -c --tool internal:fail
   rebasing 2:e4e3f3546619 tip "c2b"
   note: not rebasing 2:e4e3f3546619 tip "c2b", its destination already has all its changes
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -2058,7 +2058,7 @@ 
   $ hg rebase -s 3 -d 5
   abort: rebase in progress
   (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
-  [255]
+  [20]
   $ hg rebase --stop --continue
   abort: cannot specify both --stop and --continue
   [10]
diff --git a/tests/test-rebase-named-branches.t b/tests/test-rebase-named-branches.t
--- a/tests/test-rebase-named-branches.t
+++ b/tests/test-rebase-named-branches.t
@@ -392,7 +392,7 @@ 
   $ hg rebase -s 5 -d 4 --dry-run
   starting dry-run rebase; repository will not be changed
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg diff
   diff -r 2b586e70108d A
   --- a/A	Thu Jan 01 00:00:00 1970 +0000
@@ -406,7 +406,7 @@ 
   $ echo n | hg rebase -s 5 -d 4 --confirm --config ui.interactive=True
   starting in-memory rebase
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg diff
   diff -r 2b586e70108d A
   --- a/A	Thu Jan 01 00:00:00 1970 +0000
@@ -417,7 +417,7 @@ 
   $ hg rebase -s 5 -d 4 --confirm
   starting in-memory rebase
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg diff
   diff -r 2b586e70108d A
   --- a/A	Thu Jan 01 00:00:00 1970 +0000
diff --git a/tests/test-rebase-inmemory.t b/tests/test-rebase-inmemory.t
--- a/tests/test-rebase-inmemory.t
+++ b/tests/test-rebase-inmemory.t
@@ -484,7 +484,7 @@ 
   transaction abort!
   rollback completed
   abort: uncommitted changes
-  [255]
+  [20]
   $ cat a
   dirty
 
@@ -503,7 +503,7 @@ 
   $ hg rebase -s 2 -d 7
   abort: outstanding uncommitted merge
   (use 'hg commit' or 'hg merge --abort')
-  [255]
+  [20]
   $ hg resolve -l
   U e
 
@@ -893,7 +893,7 @@ 
   $ hg rebase -r 3 -d 1 -t:merge3
   abort: rebase in progress
   (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
-  [255]
+  [20]
   $ hg resolve --list
   U foo
   $ hg resolve --all --re-merge -t:other
diff --git a/tests/test-rebase-conflicts.t b/tests/test-rebase-conflicts.t
--- a/tests/test-rebase-conflicts.t
+++ b/tests/test-rebase-conflicts.t
@@ -57,7 +57,7 @@ 
 
   $ hg rebase --continue
   abort: no rebase in progress
-  [255]
+  [20]
 
 Conflicting rebase:
 
diff --git a/tests/test-rebase-abort.t b/tests/test-rebase-abort.t
--- a/tests/test-rebase-abort.t
+++ b/tests/test-rebase-abort.t
@@ -328,7 +328,7 @@ 
   $ hg up 1               # user gets an error saying to run hg rebase --abort
   abort: rebase in progress
   (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
-  [255]
+  [20]
 
   $ cat a
   new
@@ -398,20 +398,20 @@ 
   $ hg rebase -s 3 -d tip
   abort: rebase in progress
   (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
-  [255]
+  [20]
   $ hg up .
   abort: rebase in progress
   (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
-  [255]
+  [20]
   $ hg up -C .
   abort: rebase in progress
   (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
-  [255]
+  [20]
 
   $ hg graft 3
   abort: rebase in progress
   (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
-  [255]
+  [20]
 
   $ hg abort
   saved backup bundle to $TESTTMP/interrupted/.hg/strip-backup/3d8812cf300d-93041a90-backup.hg
diff --git a/tests/test-qrecord.t b/tests/test-qrecord.t
--- a/tests/test-qrecord.t
+++ b/tests/test-qrecord.t
@@ -470,4 +470,4 @@ 
   > EOF
   abort: histedit in progress
   (use 'hg histedit --continue' or 'hg histedit --abort')
-  [255]
+  [20]
diff --git a/tests/test-pull-update.t b/tests/test-pull-update.t
--- a/tests/test-pull-update.t
+++ b/tests/test-pull-update.t
@@ -29,7 +29,7 @@ 
   new changesets 107cefe13e42
   1 local changesets published
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg --config extensions.strip= strip --no-backup tip
   $ hg co -qC tip
 
diff --git a/tests/test-pathconflicts-update.t b/tests/test-pathconflicts-update.t
--- a/tests/test-pathconflicts-update.t
+++ b/tests/test-pathconflicts-update.t
@@ -129,7 +129,7 @@ 
   R base
   $ hg up --check dir
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg up dir
   a: path conflict - a file or link has the same name as a directory
   the local file has been renamed to a~d20a80d4def3
@@ -154,7 +154,7 @@ 
   $ echo 9 > a/b/c
   $ hg up file2 --check --config merge.checkunknown=warn
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg up file2 --clean
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark file2)
diff --git a/tests/test-mq.t b/tests/test-mq.t
--- a/tests/test-mq.t
+++ b/tests/test-mq.t
@@ -812,7 +812,7 @@ 
   $ hg add y
   $ hg strip tip
   abort: uncommitted changes
-  [255]
+  [20]
 
 --force strip with local changes
 
diff --git a/tests/test-merge5.t b/tests/test-merge5.t
--- a/tests/test-merge5.t
+++ b/tests/test-merge5.t
@@ -16,7 +16,7 @@ 
   $ rm b
   $ hg update -c 2
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg revert b
   $ hg update -c 2
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-merge1.t b/tests/test-merge1.t
--- a/tests/test-merge1.t
+++ b/tests/test-merge1.t
@@ -36,7 +36,7 @@ 
   $ hg ci
   abort: last update was interrupted
   (use 'hg update' to get a consistent checkout)
-  [255]
+  [20]
   $ hg sum
   parent: 0:538afb845929 
    commit #0
diff --git a/tests/test-merge-subrepos.t b/tests/test-merge-subrepos.t
--- a/tests/test-merge-subrepos.t
+++ b/tests/test-merge-subrepos.t
@@ -46,7 +46,7 @@ 
 
   $ hg up --check -r '.^'
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg st -S
   ! a
   $ hg up -Cq .
diff --git a/tests/test-largefiles-misc.t b/tests/test-largefiles-misc.t
--- a/tests/test-largefiles-misc.t
+++ b/tests/test-largefiles-misc.t
@@ -125,7 +125,7 @@ 
   $ hg clone -q . ../fetch
   $ hg --config extensions.fetch= fetch ../fetch
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg up -qC
   $ cd ..
 
diff --git a/tests/test-histedit-no-change.t b/tests/test-histedit-no-change.t
--- a/tests/test-histedit-no-change.t
+++ b/tests/test-histedit-no-change.t
@@ -169,7 +169,7 @@ 
   $ hg up 0
   abort: histedit in progress
   (use 'hg histedit --continue' or 'hg histedit --abort')
-  [255]
+  [20]
   $ mv .hg/histedit-state .hg/histedit-state-ignore
   $ hg up 0
   0 files updated, 0 files merged, 3 files removed, 0 files unresolved
diff --git a/tests/test-histedit-merge-tools.t b/tests/test-histedit-merge-tools.t
--- a/tests/test-histedit-merge-tools.t
+++ b/tests/test-histedit-merge-tools.t
@@ -60,7 +60,7 @@ 
   7181f42b8fca: skipping changeset (no changes)
   $ hg histedit --abort
   abort: no histedit in progress
-  [255]
+  [20]
   $ cd ..
 
 Test legacy config name
diff --git a/tests/test-histedit-edit.t b/tests/test-histedit-edit.t
--- a/tests/test-histedit-edit.t
+++ b/tests/test-histedit-edit.t
@@ -63,7 +63,7 @@ 
   $ hg histedit 177f92b77385 --commands - 2>&1 << EOF
   > EOF
   abort: uncommitted changes
-  [255]
+  [20]
   $ echo g > g
 
 edit the history
@@ -82,7 +82,7 @@ 
   $ hg update tip
   abort: histedit in progress
   (use 'hg histedit --continue' or 'hg histedit --abort')
-  [255]
+  [20]
 
 edit the plan via the editor
   $ cat >> $TESTTMP/editplan.sh <<EOF
@@ -136,7 +136,7 @@ 
   $ hg up 0
   abort: histedit in progress
   (use 'hg histedit --continue' or 'hg histedit --abort')
-  [255]
+  [20]
 
 Try to delete necessary commit
   $ hg strip -r 652413b
@@ -153,7 +153,7 @@ 
   $ hg --config extensions.mq= qnew please-fail
   abort: histedit in progress
   (use 'hg histedit --continue' or 'hg histedit --abort')
-  [255]
+  [20]
   $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle
 
   $ hg log --graph
diff --git a/tests/test-histedit-arguments.t b/tests/test-histedit-arguments.t
--- a/tests/test-histedit-arguments.t
+++ b/tests/test-histedit-arguments.t
@@ -55,11 +55,11 @@ 
 
   $ hg histedit --continue
   abort: no histedit in progress
-  [255]
+  [20]
   $ hg abort
   abort: no histedit in progress (abortflag !)
   abort: no operation in progress (abortcommand !)
-  [255]
+  [20]
 
 Run a dummy edit to make sure we get tip^^ correctly via revsingle.
 --------------------------------------------------------------------
@@ -156,7 +156,7 @@ 
   $ hg graft --continue
   abort: no graft in progress
   (continue: hg histedit --continue)
-  [255]
+  [20]
 
   $ mv .hg/histedit-state .hg/histedit-state.back
   $ hg update --quiet --clean 2
@@ -505,7 +505,7 @@ 
   $ hg commit --amend -m 'reject this fold'
   abort: histedit in progress
   (use 'hg histedit --continue' or 'hg histedit --abort')
-  [255]
+  [20]
 
 With markers enabled, histedit does not get confused, and
 amend should not be blocked by the ongoing histedit.
diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -85,7 +85,7 @@ 
   $ hg rm -q e
   $ hg graft --continue
   abort: no graft in progress
-  [255]
+  [20]
   $ hg revert -r . -q e
 
 Need to specify a rev:
@@ -130,7 +130,7 @@ 
   $ echo foo > a
   $ hg graft 1
   abort: uncommitted changes
-  [255]
+  [20]
   $ hg revert a
 
 Graft a rename:
@@ -292,7 +292,7 @@ 
   $ hg ci -m 'commit interrupted graft'
   abort: graft in progress
   (use 'hg graft --continue' or 'hg graft --stop' to stop)
-  [255]
+  [20]
 
 Abort the graft and try committing:
 
diff --git a/tests/test-graft-interrupted.t b/tests/test-graft-interrupted.t
--- a/tests/test-graft-interrupted.t
+++ b/tests/test-graft-interrupted.t
@@ -238,7 +238,7 @@ 
 
   $ hg graft --stop
   abort: no interrupted graft found
-  [255]
+  [20]
 
   $ hg graft -r 3
   grafting 3:9150fe93bec6 "added d"
@@ -342,7 +342,7 @@ 
   $ hg abort
   abort: no interrupted graft to abort (abortflag !)
   abort: no operation in progress (abortcommand !)
-  [255]
+  [20]
 
 when stripping is required
   $ hg graft -r 4 -r 5
diff --git a/tests/test-fix.t b/tests/test-fix.t
--- a/tests/test-fix.t
+++ b/tests/test-fix.t
@@ -879,7 +879,7 @@ 
   $ hg --config extensions.rebase= fix -r .
   abort: rebase in progress
   (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
-  [255]
+  [20]
 
   $ cd ..
 
diff --git a/tests/test-fetch.t b/tests/test-fetch.t
--- a/tests/test-fetch.t
+++ b/tests/test-fetch.t
@@ -157,7 +157,7 @@ 
 
   $ hg --cwd i fetch ../h
   abort: uncommitted changes
-  [255]
+  [20]
 
 test fetch with named branches
 
diff --git a/tests/test-commit-unresolved.t b/tests/test-commit-unresolved.t
--- a/tests/test-commit-unresolved.t
+++ b/tests/test-commit-unresolved.t
@@ -47,7 +47,7 @@ 
   $ hg abort
   abort: no merge in progress (abortflag !)
   abort: no operation in progress (abortcommand !)
-  [255]
+  [20]
 
   $ hg merge
   merging A
diff --git a/tests/test-branch-change.t b/tests/test-branch-change.t
--- a/tests/test-branch-change.t
+++ b/tests/test-branch-change.t
@@ -65,7 +65,7 @@ 
   $ echo bar > a
   $ hg branch -r . foo
   abort: uncommitted changes
-  [255]
+  [20]
 
   $ hg revert --all
   reverting a
@@ -331,7 +331,7 @@ 
   (branch merge, don't forget to commit)
   $ hg branch -r . abcd
   abort: outstanding uncommitted merge
-  [255]
+  [20]
 
   $ hg ci -m "Merge commit"
   $ hg glog -r 'parents(.)::'
diff --git a/tests/test-absorb-unfinished.t b/tests/test-absorb-unfinished.t
--- a/tests/test-absorb-unfinished.t
+++ b/tests/test-absorb-unfinished.t
@@ -26,5 +26,5 @@ 
   $ hg --config extensions.rebase= absorb
   abort: rebase in progress
   (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
-  [255]
+  [20]
 
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -224,6 +224,8 @@ 
     except error.Abort as inst:
         if isinstance(inst, error.InputError):
             detailed_exit_code = 10
+        elif isinstance(inst, error.StateError):
+            detailed_exit_code = 20
         ui.error(_(b"abort: %s\n") % inst.message)
         if inst.hint:
             ui.error(_(b"(%s)\n") % inst.hint)
diff --git a/mercurial/error.py b/mercurial/error.py
--- a/mercurial/error.py
+++ b/mercurial/error.py
@@ -188,6 +188,13 @@ 
     """
 
 
+class StateError(Abort):
+    """Indicates that the operation might work if retried in a different state.
+
+    Examples: Unresolved merge conflicts, unfinished operations.
+    """
+
+
 class HookLoadError(Abort):
     """raised when loading a hook fails, aborting an operation
 
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -187,7 +187,7 @@ 
     dryrun = opts.get('dry_run')
     abortstate = cmdutil.getunfinishedstate(repo)
     if not abortstate:
-        raise error.Abort(_(b'no operation in progress'))
+        raise error.StateError(_(b'no operation in progress'))
     if not abortstate.abortfunc:
         raise error.InputError(
             (
@@ -1065,7 +1065,7 @@ 
             try:
                 node = state[b'current'][0]
             except LookupError:
-                raise error.Abort(
+                raise error.StateError(
                     _(
                         b'current bisect revision is unknown - '
                         b'start a new bisect to fix'
@@ -1074,7 +1074,7 @@ 
         else:
             node, p2 = repo.dirstate.parents()
             if p2 != nullid:
-                raise error.Abort(_(b'current bisect revision is a merge'))
+                raise error.StateError(_(b'current bisect revision is a merge'))
         if rev:
             node = repo[scmutil.revsingle(repo, rev, node)].node()
         with hbisect.restore_state(repo, state, node):
@@ -1127,7 +1127,7 @@ 
                 state[b'current'] = [extendnode.node()]
                 hbisect.save_state(repo, state)
                 return mayupdate(repo, extendnode.node())
-        raise error.Abort(_(b"nothing to extend"))
+        raise error.StateError(_(b"nothing to extend"))
 
     if changesets == 0:
         hbisect.printresult(ui, repo, state, displayer, nodes, good)
@@ -2335,9 +2335,9 @@ 
     dryrun = opts.get('dry_run')
     contstate = cmdutil.getunfinishedstate(repo)
     if not contstate:
-        raise error.Abort(_(b'no operation in progress'))
+        raise error.StateError(_(b'no operation in progress'))
     if not contstate.continuefunc:
-        raise error.Abort(
+        raise error.StateError(
             (
                 _(b"%s in progress but does not support 'hg continue'")
                 % (contstate._opname)
@@ -3270,7 +3270,7 @@ 
 def _stopgraft(ui, repo, graftstate):
     """stop the interrupted graft"""
     if not graftstate.exists():
-        raise error.Abort(_(b"no interrupted graft found"))
+        raise error.StateError(_(b"no interrupted graft found"))
     pctx = repo[b'.']
     mergemod.clean_update(pctx)
     graftstate.delete()
@@ -4767,7 +4767,7 @@ 
     if abort:
         state = cmdutil.getunfinishedstate(repo)
         if state and state._opname != b'merge':
-            raise error.Abort(
+            raise error.StateError(
                 _(b'cannot abort merge with %s in progress') % (state._opname),
                 hint=state.hint(),
             )
@@ -5893,7 +5893,7 @@ 
         ms = mergestatemod.mergestate.read(repo)
 
         if not (ms.active() or repo.dirstate.p2() != nullid):
-            raise error.Abort(
+            raise error.StateError(
                 _(b'resolve command not applicable when not merging')
             )
 
@@ -5985,7 +5985,7 @@ 
                 )
             )
             if markcheck == b'abort' and not all and not pats:
-                raise error.Abort(
+                raise error.StateError(
                     _(b'conflict markers detected'),
                     hint=_(b'use --all to mark anyway'),
                 )
@@ -7185,7 +7185,7 @@ 
         if not opts.get(b'local'):
             p1, p2 = repo.dirstate.parents()
             if p2 != nullid:
-                raise error.Abort(_(b'uncommitted merge'))
+                raise error.StateError(_(b'uncommitted merge'))
             bheads = repo.branchheads()
             if not opts.get(b'force') and bheads and p1 not in bheads:
                 raise error.InputError(
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1087,10 +1087,10 @@ 
     """
 
     if merge and repo.dirstate.p2() != nullid:
-        raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
+        raise error.StateError(_(b'outstanding uncommitted merge'), hint=hint)
     st = repo.status()
     if st.modified or st.added or st.removed or st.deleted:
-        raise error.Abort(_(b'uncommitted changes'), hint=hint)
+        raise error.StateError(_(b'uncommitted changes'), hint=hint)
     ctx = repo[None]
     for s in sorted(ctx.substate):
         ctx.sub(s).bailifchanged(hint=hint)
@@ -3738,7 +3738,7 @@ 
         ):
             continue
         if state.isunfinished(repo):
-            raise error.Abort(state.msg(), hint=state.hint())
+            raise error.StateError(state.msg(), hint=state.hint())
 
     for s in statemod._unfinishedstates:
         if (
@@ -3749,7 +3749,7 @@ 
         ):
             continue
         if s.isunfinished(repo):
-            raise error.Abort(s.msg(), hint=s.hint())
+            raise error.StateError(s.msg(), hint=s.hint())
 
 
 def clearunfinished(repo):
@@ -3760,7 +3760,7 @@ 
         if state._reportonly:
             continue
         if not state._clearable and state.isunfinished(repo):
-            raise error.Abort(state.msg(), hint=state.hint())
+            raise error.StateError(state.msg(), hint=state.hint())
 
     for s in statemod._unfinishedstates:
         if s._opname == b'merge' or state._reportonly:
@@ -3829,14 +3829,14 @@ 
     hint = None
     if after[1]:
         hint = after[0]
-    raise error.Abort(_(b'no %s in progress') % task, hint=hint)
+    raise error.StateError(_(b'no %s in progress') % task, hint=hint)
 
 
 def abortgraft(ui, repo, graftstate):
     """abort the interrupted graft and rollbacks to the state before interrupted
     graft"""
     if not graftstate.exists():
-        raise error.Abort(_(b"no interrupted graft to abort"))
+        raise error.StateError(_(b"no interrupted graft to abort"))
     statedata = readgraftstate(repo, graftstate)
     newnodes = statedata.get(b'newnodes')
     if newnodes is None:
diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -804,10 +804,10 @@ 
         raise error.Abort(_(b'no revision checked out'))
     if opts.get(b'continue'):
         if not tp.canresume():
-            raise error.Abort(_(b'no transplant to continue'))
+            raise error.StateError(_(b'no transplant to continue'))
     elif opts.get(b'stop'):
         if not tp.canresume():
-            raise error.Abort(_(b'no interrupted transplant found'))
+            raise error.StateError(_(b'no interrupted transplant found'))
         return tp.stop(ui, repo)
     else:
         cmdutil.checkunfinished(repo)