D10936: cmdutil: fix newandmodified file accounting for --interactive commits

2021-07-02 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  `originalchunks` is a misleading name, because it only contains header 
objects, which are flattened to selected hunks by the filter function. As such, 
`chunks not in originalchunks` is always True and misleading, because hunk 
objects never compare equal to header objects. This change fixes the internal 
naming and removes the useless parameter from the method.
  
  This change also fixes https://bz.mercurial-scm.org/show_bug.cgi?id=6533, by 
considering the filtered headers, rather than the hunks, when determining new 
and modified files. If a file is renamed + edited, and the edited hunks are 
deselected (but the file is not), the filtered chunks will contain a header for 
the file (because it's .special()) but but no hunks.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/cmdutil.py
  tests/test-commit-interactive.t

CHANGE DETAILS

diff --git a/tests/test-commit-interactive.t b/tests/test-commit-interactive.t
--- a/tests/test-commit-interactive.t
+++ b/tests/test-commit-interactive.t
@@ -1713,20 +1713,58 @@
   record this change to 'plain3'?
   (enter ? for help) [Ynesfdaq?] y
   
+
+Rename file but discard edits
+
+  $ echo content > new-file
+  $ hg add -q new-file
+  $ hg commit -qm 'new file'
+  $ hg mv new-file renamed-file
+  $ echo new-content >> renamed-file
+  $ hg commit -i -d '24 0' -m content-rename< y
+  > n
+  > EOF
+  diff --git a/new-file b/renamed-file
+  rename from new-file
+  rename to renamed-file
+  1 hunks, 1 lines changed
+  examine changes to 'new-file' and 'renamed-file'?
+  (enter ? for help) [Ynesfdaq?] y
+  
+  @@ -1,1 +1,2 @@
+   content
+  +new-content
+  record this change to 'renamed-file'?
+  (enter ? for help) [Ynesfdaq?] n
+  
+  $ hg status
+  M renamed-file
+  ? editedfile.orig
+  ? editedfile.rej
+  ? editor.sh
+  $ hg diff
+  diff -r a006b0d78ce9 renamed-file
+  --- a/renamed-file   Thu Jan 01 00:00:24 1970 +
+  +++ b/renamed-file   Thu Jan 01 00:00:00 1970 +
+  @@ -1,1 +1,2 @@
+   content
+  +new-content
+
 The #if execbit block above changes the hash here on some systems
   $ hg status -A plain3
   C plain3
   $ hg tip
-  changeset:   32:* (glob)
+  changeset:   34:a006b0d78ce9
   tag: tip
   user:test
-  date:Thu Jan 01 00:00:23 1970 +
-  summary: moving_files
+  date:Thu Jan 01 00:00:24 1970 +
+  summary: content-rename
   
 Editing patch of newly added file
 
   $ hg update -C .
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat > editor.sh << '__EOF__'
   > cat "$1"  | sed "s/first/very/g"  > tt
   > mv tt  "$1"
@@ -1737,7 +1775,7 @@
   > This is the third line
   > __EOF__
   $ hg add newfile
-  $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' 
-medit-patch-new < y
   > e
   > EOF
@@ -1770,7 +1808,7 @@
   $ cd folder
   $ echo "foo" > bar
   $ hg add bar
-  $ hg commit -i -d '23 0' -mnewfilesubdir  < y
   > y
   > EOF
@@ -1786,15 +1824,15 @@
   
 The #if execbit block above changes the hashes here on some systems
   $ hg tip -p
-  changeset:   34:* (glob)
+  changeset:   36:6d2ed8a25e2a
   tag: tip
   user:test
-  date:Thu Jan 01 00:00:23 1970 +
+  date:Thu Jan 01 00:00:26 1970 +
   summary: newfilesubdir
   
   diff -r * -r * folder/bar (glob)
   --- /dev/nullThu Jan 01 00:00:00 1970 +
-  +++ b/folder/bar Thu Jan 01 00:00:23 1970 +
+  +++ b/folder/bar Thu Jan 01 00:00:26 1970 +
   @@ -0,0 +1,1 @@
   +foo
   
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -346,18 +346,19 @@
 return isinstance(x, hunkclasses)
 
 
-def newandmodified(chunks, originalchunks):
+def isheader(x):
+headerclasses = (crecordmod.uiheader, patch.header)
+return isinstance(x, headerclasses)
+
+
+def newandmodified(chunks):
 newlyaddedandmodifiedfiles = set()
 alsorestore = set()
 for chunk in chunks:
-if (
-ishunk(chunk)
-and chunk.header.isnewfile()
-and chunk not in originalchunks
-):
-newlyaddedandmodifiedfiles.add(chunk.header.filename())
+if isheader(chunk) and chunk.isnewfile():
+newlyaddedandmodifiedfiles.add(chunk.filename())
 alsorestore.update(
-set(chunk.header.files()) - {chunk.header.filename()}
+set(chunk.files()) - {chunk.filename()}
 )
 return newlyaddedandmodifiedfiles, alsorestore
 
@@ -517,12 +518,12 @@
 diffopts.git = True
 diffopts.showfunc = True
 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
-originalchunks = 

D8730: error: unify the error message formats for 'rebase' and 'unshelve'

2020-07-10 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a reviewer: durin42.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/error.py
  tests/test-absorb-unfinished.t
  tests/test-bookmarks-rebase.t
  tests/test-copytrace-heuristics.t
  tests/test-fix.t
  tests/test-largefiles-update.t
  tests/test-merge-halt.t
  tests/test-narrow-rebase.t
  tests/test-rebase-abort.t
  tests/test-rebase-backup.t
  tests/test-rebase-bookmarks.t
  tests/test-rebase-check-restore.t
  tests/test-rebase-collapse.t
  tests/test-rebase-conflicts.t
  tests/test-rebase-dest.t
  tests/test-rebase-detach.t
  tests/test-rebase-inmemory.t
  tests/test-rebase-interruptions.t
  tests/test-rebase-mq-skip.t
  tests/test-rebase-mq.t
  tests/test-rebase-obsolete.t
  tests/test-rebase-parameters.t
  tests/test-rebase-partial.t
  tests/test-rebase-transaction.t
  tests/test-resolve.t
  tests/test-sparse-profiles.t
  tests/test-sparse.t

CHANGE DETAILS

diff --git a/tests/test-sparse.t b/tests/test-sparse.t
--- a/tests/test-sparse.t
+++ b/tests/test-sparse.t
@@ -200,7 +200,7 @@
   temporarily included 2 file(s) in the sparse checkout for merging
   merging hide
   warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
   $ hg debugsparse
diff --git a/tests/test-sparse-profiles.t b/tests/test-sparse-profiles.t
--- a/tests/test-sparse-profiles.t
+++ b/tests/test-sparse-profiles.t
@@ -200,7 +200,7 @@
   merging data.py
   warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve 
--mark')
   warning: conflicts while merging data.py! (edit, then use 'hg resolve 
--mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
   $ rm *.orig
   $ ls -A
diff --git a/tests/test-resolve.t b/tests/test-resolve.t
--- a/tests/test-resolve.t
+++ b/tests/test-resolve.t
@@ -520,7 +520,7 @@
   warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
   warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
   warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
 Test when commands.resolve.confirm config option is not set:
diff --git a/tests/test-rebase-transaction.t b/tests/test-rebase-transaction.t
--- a/tests/test-rebase-transaction.t
+++ b/tests/test-rebase-transaction.t
@@ -107,7 +107,7 @@
   rebasing 3:c26739dbe603 "C" (C)
   merging conflict
   warning: conflicts while merging conflict! (edit, then use 'hg resolve 
--mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
   $ hg tglog
   o  5: D
diff --git a/tests/test-rebase-partial.t b/tests/test-rebase-partial.t
--- a/tests/test-rebase-partial.t
+++ b/tests/test-rebase-partial.t
@@ -84,7 +84,7 @@
   rebasing 2:ef8c0fe0897b "D" (D)
   merging file
   warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
   $ hg rebase --abort
   rebase aborted
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
@@ -479,7 +479,7 @@
 
   $ hg rebase -s 2 -d 1 --tool internal:fail
   rebasing 2:e4e3f3546619 "c2b" (tip)
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
   $ hg summary
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
@@ -1032,7 +1032,7 @@
   rebasing 19:b82fb57ea638 "willconflict second version"
   merging willconflict
   warning: conflicts while merging willconflict! (edit, then use 'hg resolve 
--mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
   $ hg resolve --mark willconflict
@@ -1787,7 +1787,7 @@
   rebasing 1:2ec65233581b "B"
   merging D
   warning: conflicts while merging D! (edit, then use 'hg resolve --mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
   $ cp -R . $TESTTMP/hidden-state2
@@ -1872,7 +1872,7 @@
   rebasing 3:055a42cdd887 "d"
   merging d
   warning: conflicts while merging d! (edit, then 

D8713: error: normalize "unresolved conflicts" error messages with a custom class

2020-07-08 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a reviewer: durin42.
Herald added a reviewer: martinvonz.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  hgext/rebase.py
  mercurial/error.py
  mercurial/shelve.py
  tests/test-absorb-unfinished.t
  tests/test-bookmarks-rebase.t
  tests/test-copytrace-heuristics.t
  tests/test-fix.t
  tests/test-largefiles-update.t
  tests/test-merge-halt.t
  tests/test-narrow-rebase.t
  tests/test-rebase-abort.t
  tests/test-rebase-backup.t
  tests/test-rebase-bookmarks.t
  tests/test-rebase-check-restore.t
  tests/test-rebase-collapse.t
  tests/test-rebase-conflicts.t
  tests/test-rebase-dest.t
  tests/test-rebase-detach.t
  tests/test-rebase-inmemory.t
  tests/test-rebase-interruptions.t
  tests/test-rebase-mq-skip.t
  tests/test-rebase-mq.t
  tests/test-rebase-obsolete.t
  tests/test-rebase-parameters.t
  tests/test-rebase-partial.t
  tests/test-rebase-transaction.t
  tests/test-resolve.t
  tests/test-sparse-profiles.t
  tests/test-sparse.t

CHANGE DETAILS

diff --git a/tests/test-sparse.t b/tests/test-sparse.t
--- a/tests/test-sparse.t
+++ b/tests/test-sparse.t
@@ -200,7 +200,7 @@
   temporarily included 2 file(s) in the sparse checkout for merging
   merging hide
   warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
   $ hg debugsparse
diff --git a/tests/test-sparse-profiles.t b/tests/test-sparse-profiles.t
--- a/tests/test-sparse-profiles.t
+++ b/tests/test-sparse-profiles.t
@@ -200,7 +200,7 @@
   merging data.py
   warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve 
--mark')
   warning: conflicts while merging data.py! (edit, then use 'hg resolve 
--mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
   $ rm *.orig
   $ ls -A
diff --git a/tests/test-resolve.t b/tests/test-resolve.t
--- a/tests/test-resolve.t
+++ b/tests/test-resolve.t
@@ -520,7 +520,7 @@
   warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
   warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
   warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
 Test when commands.resolve.confirm config option is not set:
diff --git a/tests/test-rebase-transaction.t b/tests/test-rebase-transaction.t
--- a/tests/test-rebase-transaction.t
+++ b/tests/test-rebase-transaction.t
@@ -107,7 +107,7 @@
   rebasing 3:c26739dbe603 "C" (C)
   merging conflict
   warning: conflicts while merging conflict! (edit, then use 'hg resolve 
--mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
   $ hg tglog
   o  5: D
diff --git a/tests/test-rebase-partial.t b/tests/test-rebase-partial.t
--- a/tests/test-rebase-partial.t
+++ b/tests/test-rebase-partial.t
@@ -84,7 +84,7 @@
   rebasing 2:ef8c0fe0897b "D" (D)
   merging file
   warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
   $ hg rebase --abort
   rebase aborted
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
@@ -479,7 +479,7 @@
 
   $ hg rebase -s 2 -d 1 --tool internal:fail
   rebasing 2:e4e3f3546619 "c2b" (tip)
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
   $ hg summary
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
@@ -1032,7 +1032,7 @@
   rebasing 19:b82fb57ea638 "willconflict second version"
   merging willconflict
   warning: conflicts while merging willconflict! (edit, then use 'hg resolve 
--mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
   $ hg resolve --mark willconflict
@@ -1787,7 +1787,7 @@
   rebasing 1:2ec65233581b "B"
   merging D
   warning: conflicts while merging D! (edit, then use 'hg resolve --mark')
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
   [1]
 
   $ cp -R . $TESTTMP/hidden-state2
@@ -1872,7 +1872,7 @@
   rebasing 

D8712: black: format some files in preparation for subsequent changes

2020-07-08 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a reviewer: martinvonz.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  hgext/rebase.py
  mercurial/error.py
  mercurial/shelve.py
  mercurial/state.py

CHANGE DETAILS

diff --git a/mercurial/state.py b/mercurial/state.py
--- a/mercurial/state.py
+++ b/mercurial/state.py
@@ -4,10 +4,7 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-
-"""
-This file contains class to wrap the state for commands and other
-related logic.
+"""This file contains class to wrap the state for commands and other related 
logic.
 
 All the data related to the command state is stored as dictionary in the 
object.
 The class has methods using which the data can be stored to disk in a file 
under
@@ -21,18 +18,11 @@
 
 from .i18n import _
 
-from . import (
-error,
-pycompat,
-util,
-)
+from . import error, pycompat, util
 from .utils import cborutil
 
 if pycompat.TYPE_CHECKING:
-from typing import (
-Any,
-Dict,
-)
+from typing import Any, Dict
 
 for t in (Any, Dict):
 assert t
@@ -40,6 +30,7 @@
 
 class cmdstate(object):
 """a wrapper class to store the state of commands like `rebase`, `graft`,
+
 `histedit`, `shelve` etc. Extensions can also use this to write state 
files.
 
 All the data for the state is stored in the form of key-value pairs in a
@@ -54,6 +45,7 @@
 
 def __init__(self, repo, fname):
 """ repo is the repo object
+
 fname is the file name in which data should be stored in .hg directory
 """
 self._repo = repo
@@ -71,7 +63,7 @@
 """
 if not isinstance(version, int):
 raise error.ProgrammingError(
-b"version of state file should be an integer"
+b'version of state file should be an integer'
 )
 
 with self._repo.vfs(self.fname, b'wb', atomictemp=True) as fp:
@@ -81,13 +73,15 @@
 
 def _read(self):
 """reads the state file and returns a dictionary which contain
-data in the same format as it was before storing"""
+
+data in the same format as it was before storing
+"""
 with self._repo.vfs(self.fname, b'rb') as fp:
 try:
 int(fp.readline())
 except ValueError:
 raise error.CorruptedState(
-b"unknown version of state file found"
+b'unknown version of state file found'
 )
 
 return cborutil.decodeall(fp.read())[0]
@@ -103,6 +97,7 @@
 
 class _statecheck(object):
 """a utility class that deals with multistep operations like graft,
+
histedit, bisect, update etc and check whether such commands
are in an unfinished conditition or not and return appropriate message
and hint.
@@ -140,6 +135,7 @@
 
 def statusmsg(self):
 """returns the hint message corresponding to the command for
+
 hg status --verbose
 """
 if not self._statushint:
@@ -156,6 +152,7 @@
 
 def hint(self):
 """returns the hint message corresponding to an interrupted
+
 operation
 """
 if not self._cmdhint:
@@ -177,6 +174,7 @@
 
 def isunfinished(self, repo):
 """determines whether a multi-step operation is in progress
+
 or not
 """
 if self._opname == b'merge':
@@ -197,13 +195,14 @@
 reportonly=False,
 continueflag=False,
 stopflag=False,
-cmdmsg=b"",
-cmdhint=b"",
-statushint=b"",
+cmdmsg=b'',
+cmdhint=b'',
+statushint=b'',
 abortfunc=None,
 continuefunc=None,
 ):
 """this registers a new command or operation to unfinishedstates
+
 opname is the name the command or operation
 fname is the file name in which data should be stored in .hg directory.
 It is None for merge command.
@@ -256,7 +255,7 @@
 clearable=True,
 cmdmsg=_(b'last update was interrupted'),
 cmdhint=_(b"use 'hg update' to get a consistent checkout"),
-statushint=_(b"To continue:hg update ."),
+statushint=_(b'To continue:hg update .'),
 )
 addunfinished(
 b'bisect',
diff --git a/mercurial/shelve.py b/mercurial/shelve.py
--- a/mercurial/shelve.py
+++ b/mercurial/shelve.py
@@ -53,10 +53,7 @@
 util,
 vfs as vfsmod,
 )
-from .utils import (
-dateutil,
-stringutil,
-)
+from .utils import dateutil, stringutil
 
 backupdir = b'shelve-backup'
 shelvedir = b'shelved'
diff --git a/mercurial/error.py b/mercurial/error.py
--- a/mercurial/error.py
+++ b/mercurial/error.py
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-
 

D8714: state: support validated declaration of nested unfinished ops

2020-07-08 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  This enables extensions to define commands that delgate to rebase, evolve, 
etc. one or more times to also have their own unfinished states for the full 
sequence of operations without monkey-patching _unfinishedstates.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  mercurial/state.py

CHANGE DETAILS

diff --git a/mercurial/state.py b/mercurial/state.py
--- a/mercurial/state.py
+++ b/mercurial/state.py
@@ -16,6 +16,8 @@
 
 from __future__ import absolute_import
 
+import contextlib
+
 from .i18n import _
 
 from . import error, pycompat, util
@@ -114,6 +116,7 @@
 reportonly,
 continueflag,
 stopflag,
+childopnames,
 cmdmsg,
 cmdhint,
 statushint,
@@ -127,6 +130,7 @@
 self._reportonly = reportonly
 self._continueflag = continueflag
 self._stopflag = stopflag
+self._childopnames = childopnames
 self._cmdmsg = cmdmsg
 self._cmdhint = cmdhint
 self._statushint = statushint
@@ -185,6 +189,7 @@
 
 # A list of statecheck objects for multistep operations like graft.
 _unfinishedstates = []
+_unfinishedstatesbyname = {}
 
 
 def addunfinished(
@@ -195,6 +200,7 @@
 reportonly=False,
 continueflag=False,
 stopflag=False,
+childopnames=[],
 cmdmsg=b'',
 cmdhint=b'',
 statushint=b'',
@@ -217,6 +223,8 @@
 `--continue` option or not.
 stopflag is a boolean that determines whether or not a command supports
 --stop flag
+childopnames is a list of other opnames this op uses as sub-steps of its
+own execution. They must already be added.
 cmdmsg is used to pass a different status message in case standard
 message of the format "abort: cmdname in progress" is not desired.
 cmdhint is used to pass a different hint message in case standard
@@ -237,17 +245,69 @@
 reportonly,
 continueflag,
 stopflag,
+childopnames,
 cmdmsg,
 cmdhint,
 statushint,
 abortfunc,
 continuefunc,
 )
+
 if opname == b'merge':
 _unfinishedstates.append(statecheckobj)
 else:
+# This check enforces that for any op 'foo' which depends on op 'bar',
+# 'foo' comes before 'bar' in _unfinishedstates. This ensures that
+# getrepostate() always returns the most specific applicable answer.
+for childopname in childopnames:
+if childopname not in _unfinishedstatesbyname:
+raise error.ProgrammingError(
+_(b'op %s depends on unknown op %s') % (opname, 
childopname)
+)
+
 _unfinishedstates.insert(0, statecheckobj)
 
+if opname in _unfinishedstatesbyname:
+raise error.ProgrammingError(_(b'op %s registered twice') % opname)
+_unfinishedstatesbyname[opname] = statecheckobj
+
+
+@contextlib.contextmanager
+def delegating(repo, opname, childopname):
+"""context wrapper for delegations from opname to childopname.
+
+requires that childopname was specified when opname was registered.
+
+Usage:
+  def my_command_foo_that_uses_rebase(...):
+...
+with state.delegating(repo, 'foo', 'rebase'):
+  _run_rebase(...)
+...
+"""
+
+s = _unfinishedstatesbyname[parentopname]
+if not s:
+raise error.ProgrammingError(_(b'unknown op %s') % opname)
+if childopname not in s._childopnames:
+raise error.ProgrammingError(
+_(b'op %s does not delegate to %s') % (opname, childopname)
+)
+c = _unfinishedstatesbyname[childopname]
+
+newskipstates = set(repo.ui.configlist(b'commands', b'status.skipstates'))
+newskipstates.add(childopname)
+with repo.ui.configoverride(
+{(b'commands', b'status.skipstates'): newskipstates}
+):
+try:
+yield
+except error.ConflictResolutionRequired as e:
+# Rewrite conflict resolution advice for the parent opname.
+if e.opname == childopname:
+raise error.ConflictResolutionRequired(opname)
+raise e
+
 
 addunfinished(
 b'update',



To: dploch, #hg-reviewers
Cc: mercurial-patches, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7462: py3: make doc strings containing the deprected '\.' escape sequence raw strings

2019-11-21 Thread dploch (Daniel Ploch)
Closed by commit rHGbfbbf48d51e8: py3: make doc strings containing deprecated 
\. escape sequence raw strings (authored by dploch).
This revision was automatically updated to reflect the committed changes.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7462?vs=18252=18262

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7462/new/

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

AFFECTED FILES
  mercurial/match.py

CHANGE DETAILS

diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -554,7 +554,7 @@
 
 
 class patternmatcher(basematcher):
-"""Matches a set of (kind, pat, source) against a 'root' directory.
+r"""Matches a set of (kind, pat, source) against a 'root' directory.
 
 >>> kindpats = [
 ... (b're', br'.*\.c$', b''),
@@ -1172,7 +1172,7 @@
 
 
 def patkind(pattern, default=None):
-'''If pattern is 'kind:pat' with a known kind, return kind.
+r'''If pattern is 'kind:pat' with a known kind, return kind.
 
 >>> patkind(br're:.*\.c$')
 're'



To: dploch, #hg-reviewers, dlax, pulkit
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7462: py3: make doc strings containing the deprected '\.' escape sequence raw strings

2019-11-20 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  mercurial/match.py

CHANGE DETAILS

diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -543,7 +543,7 @@
 
 
 class patternmatcher(basematcher):
-"""Matches a set of (kind, pat, source) against a 'root' directory.
+r"""Matches a set of (kind, pat, source) against a 'root' directory.
 
 >>> kindpats = [
 ... (b're', br'.*\.c$', b''),
@@ -1152,7 +1152,7 @@
 
 
 def patkind(pattern, default=None):
-'''If pattern is 'kind:pat' with a known kind, return kind.
+r'''If pattern is 'kind:pat' with a known kind, return kind.
 
 >>> patkind(br're:.*\.c$')
 're'



To: dploch, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7372: remotefilelog: handle **kwargs correctly when overriding changelog.add()

2019-11-13 Thread dploch (Daniel Ploch)
Closed by commit rHGc5f6f58f6c71: remotefilelog: handle **kwargs correctly when 
overriding changelog.add() (authored by dploch).
This revision was automatically updated to reflect the committed changes.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7372?vs=18050=18052

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7372/new/

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

AFFECTED FILES
  hgext/remotefilelog/__init__.py

CHANGE DETAILS

diff --git a/hgext/remotefilelog/__init__.py b/hgext/remotefilelog/__init__.py
--- a/hgext/remotefilelog/__init__.py
+++ b/hgext/remotefilelog/__init__.py
@@ -719,9 +719,9 @@
 remotefilelog.remotefilelog, b'addrawrevision', addrawrevision
 )
 
-def changelogadd(orig, self, *args):
+def changelogadd(orig, self, *args, **kwargs):
 oldlen = len(self)
-node = orig(self, *args)
+node = orig(self, *args, **kwargs)
 newlen = len(self)
 if oldlen != newlen:
 for oldargs in pendingfilecommits:



To: dploch, #hg-reviewers, pulkit
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D7372: remotefilelog: handle **kwargs correctly when overriding changelog.add()

2019-11-12 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  hgext/remotefilelog/__init__.py

CHANGE DETAILS

diff --git a/hgext/remotefilelog/__init__.py b/hgext/remotefilelog/__init__.py
--- a/hgext/remotefilelog/__init__.py
+++ b/hgext/remotefilelog/__init__.py
@@ -719,9 +719,9 @@
 remotefilelog.remotefilelog, b'addrawrevision', addrawrevision
 )
 
-def changelogadd(orig, self, *args):
+def changelogadd(orig, self, *args, **kwargs):
 oldlen = len(self)
-node = orig(self, *args)
+node = orig(self, *args, **kwargs)
 newlen = len(self)
 if oldlen != newlen:
 for oldargs in pendingfilecommits:



To: dploch, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5534: merge: make local file storage in the .hg/merge directory extensible

2019-01-09 Thread dploch (Daniel Ploch)
This revision was automatically updated to reflect the committed changes.
Closed by commit rHG8c222bec97da: merge: make local file storage in the 
.hg/merge directory extensible (authored by dploch, committed by ).

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D5534?vs=13086=13108

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

AFFECTED FILES
  mercurial/merge.py

CHANGE DETAILS

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -478,6 +478,13 @@
 f.write(_pack(format, key, len(data), data))
 f.close()
 
+@staticmethod
+def getlocalkey(path):
+"""hash the path of a local file context for storage in the .hg/merge
+directory."""
+
+return hex(hashlib.sha1(path).digest())
+
 def add(self, fcl, fco, fca, fd):
 """add a new (potentially?) conflicting file the merge state
 fcl: file context for local,
@@ -488,11 +495,11 @@
 note: also write the local version to the `.hg/merge` directory.
 """
 if fcl.isabsent():
-hash = nullhex
+localkey = nullhex
 else:
-hash = hex(hashlib.sha1(fcl.path()).digest())
-self._repo.vfs.write('merge/' + hash, fcl.data())
-self._state[fd] = [MERGE_RECORD_UNRESOLVED, hash, fcl.path(),
+localkey = mergestate.getlocalkey(fcl.path())
+self._repo.vfs.write('merge/' + localkey, fcl.data())
+self._state[fd] = [MERGE_RECORD_UNRESOLVED, localkey, fcl.path(),
fca.path(), hex(fca.filenode()),
fco.path(), hex(fco.filenode()),
fcl.flags()]
@@ -551,15 +558,15 @@
MERGE_RECORD_DRIVER_RESOLVED):
 return True, 0
 stateentry = self._state[dfile]
-state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
+state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
 octx = self._repo[self._other]
 extras = self.extras(dfile)
 anccommitnode = extras.get('ancestorlinknode')
 if anccommitnode:
 actx = self._repo[anccommitnode]
 else:
 actx = None
-fcd = self._filectxorabsent(hash, wctx, dfile)
+fcd = self._filectxorabsent(localkey, wctx, dfile)
 fco = self._filectxorabsent(onode, octx, ofile)
 # TODO: move this to filectxorabsent
 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
@@ -577,8 +584,8 @@
 flags = flo
 if preresolve:
 # restore local
-if hash != nullhex:
-f = self._repo.vfs('merge/' + hash)
+if localkey != nullhex:
+f = self._repo.vfs('merge/' + localkey)
 wctx[dfile].write(f.read(), flags)
 f.close()
 else:



To: dploch, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D5534: merge: make local file storage in the .hg/merge directory extensible

2019-01-08 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This is similar to remotefilelog's 'getlocalkey' method, which must be 
overridden by systems which rely on full path names for access control purposes.

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  mercurial/merge.py

CHANGE DETAILS

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -478,6 +478,13 @@
 f.write(_pack(format, key, len(data), data))
 f.close()
 
+@staticmethod
+def getlocalkey(path):
+"""hash the path of a local file context for storage in the .hg/merge
+directory."""
+
+return hex(hashlib.sha1(path).digest())
+
 def add(self, fcl, fco, fca, fd):
 """add a new (potentially?) conflicting file the merge state
 fcl: file context for local,
@@ -488,11 +495,11 @@
 note: also write the local version to the `.hg/merge` directory.
 """
 if fcl.isabsent():
-hash = nullhex
+localkey = nullhex
 else:
-hash = hex(hashlib.sha1(fcl.path()).digest())
-self._repo.vfs.write('merge/' + hash, fcl.data())
-self._state[fd] = [MERGE_RECORD_UNRESOLVED, hash, fcl.path(),
+localkey = mergestate.getlocalkey(fcl.path())
+self._repo.vfs.write('merge/' + localkey, fcl.data())
+self._state[fd] = [MERGE_RECORD_UNRESOLVED, localkey, fcl.path(),
fca.path(), hex(fca.filenode()),
fco.path(), hex(fco.filenode()),
fcl.flags()]
@@ -551,15 +558,15 @@
MERGE_RECORD_DRIVER_RESOLVED):
 return True, 0
 stateentry = self._state[dfile]
-state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
+state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
 octx = self._repo[self._other]
 extras = self.extras(dfile)
 anccommitnode = extras.get('ancestorlinknode')
 if anccommitnode:
 actx = self._repo[anccommitnode]
 else:
 actx = None
-fcd = self._filectxorabsent(hash, wctx, dfile)
+fcd = self._filectxorabsent(localkey, wctx, dfile)
 fco = self._filectxorabsent(onode, octx, ofile)
 # TODO: move this to filectxorabsent
 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
@@ -577,8 +584,8 @@
 flags = flo
 if preresolve:
 # restore local
-if hash != nullhex:
-f = self._repo.vfs('merge/' + hash)
+if localkey != nullhex:
+f = self._repo.vfs('merge/' + localkey)
 wctx[dfile].write(f.read(), flags)
 f.close()
 else:



To: dploch, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2935: fancyopts: fix rendering of customopt defaults in help text

2018-03-23 Thread dploch (Daniel Ploch)
This revision was automatically updated to reflect the committed changes.
Closed by commit rHG979c8ce9022d: fancyopts: fix rendering of customopt 
defaults in help text (authored by dploch, committed by ).

CHANGED PRIOR TO COMMIT
  https://phab.mercurial-scm.org/D2935?vs=7257=7263#toc

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2935?vs=7257=7263

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

AFFECTED FILES
  mercurial/help.py
  tests/test-help.t

CHANGE DETAILS

diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -716,15 +716,23 @@
 
   $ cat > helpext.py < import os
-  > from mercurial import commands, registrar
+  > from mercurial import commands, fancyopts, registrar
   > 
+  > def func(arg):
+  > return '%sfoo' % arg
+  > class customopt(fancyopts.customopt):
+  > def newstate(self, oldstate, newparam, abort):
+  > return '%sbar' % oldstate
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > 
   > @command(b'nohelp',
-  > [(b'', b'longdesc', 3, b'x'*90),
+  > [(b'', b'longdesc', 3, b'x'*67),
   > (b'n', b'', None, b'normal desc'),
-  > (b'', b'newline', b'', b'line1\nline2')],
+  > (b'', b'newline', b'', b'line1\nline2'),
+  > (b'', b'callableopt', func, b'adds foo'),
+  > (b'', b'customopt', customopt(''), b'adds bar'),
+  > (b'', b'customopt-withdefault', customopt('foo'), b'adds bar')],
   > b'hg nohelp',
   > norepo=True)
   > @command(b'debugoptADV', [(b'', b'aopt', None, b'option is (ADVANCED)')])
@@ -786,10 +794,14 @@
   
   options:
   
-  --longdesc VALUE 
x
-   x (default: 3)
-   -n --   normal desc
-  --newline VALUE  line1 line2
+  --longdesc VALUE
+

+xxx (default: 3)
+   -n --normal desc
+  --newline VALUE   line1 line2
+  --callableopt VALUE   adds foo
+  --customopt VALUE adds bar
+  --customopt-withdefault VALUE adds bar (default: foo)
   
   (some details hidden, use --verbose to show complete help)
 
diff --git a/mercurial/help.py b/mercurial/help.py
--- a/mercurial/help.py
+++ b/mercurial/help.py
@@ -20,6 +20,7 @@
 encoding,
 error,
 extensions,
+fancyopts,
 filemerge,
 fileset,
 minirst,
@@ -84,7 +85,10 @@
 if shortopt:
 so = '-' + shortopt
 lo = '--' + longopt
-if default:
+
+if isinstance(default, fancyopts.customopt):
+default = default.defaultvalue
+if default and not callable(default):
 # default is of unknown type, and in Python 2 we abused
 # the %s-shows-repr property to handle integers etc. To
 # match that behavior on Python 3, we do str(default) and



To: dploch, #hg-reviewers, yuja
Cc: yuja, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2937: fancyopts: prevent mutation of the default value in customopts

2018-03-23 Thread dploch (Daniel Ploch)
This revision was automatically updated to reflect the committed changes.
Closed by commit rHGef6215df2402: fancyopts: prevent mutation of the default 
value in customopts (authored by dploch, committed by ).

CHANGED PRIOR TO COMMIT
  https://phab.mercurial-scm.org/D2937?vs=7258=7264#toc

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2937?vs=7258=7264

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

AFFECTED FILES
  mercurial/fancyopts.py
  mercurial/help.py

CHANGE DETAILS

diff --git a/mercurial/help.py b/mercurial/help.py
--- a/mercurial/help.py
+++ b/mercurial/help.py
@@ -87,7 +87,7 @@
 lo = '--' + longopt
 
 if isinstance(default, fancyopts.customopt):
-default = default.defaultvalue
+default = default.getdefaultvalue()
 if default and not callable(default):
 # default is of unknown type, and in Python 2 we abused
 # the %s-shows-repr property to handle integers etc. To
diff --git a/mercurial/fancyopts.py b/mercurial/fancyopts.py
--- a/mercurial/fancyopts.py
+++ b/mercurial/fancyopts.py
@@ -208,20 +208,27 @@
 __metaclass__ = abc.ABCMeta
 
 def __init__(self, defaultvalue):
-self.defaultvalue = defaultvalue
+self._defaultvalue = defaultvalue
 
 def _isboolopt(self):
 return False
 
+def getdefaultvalue(self):
+"""Returns the default value for this opt.
+
+Subclasses should override this to return a new value if the value type
+is mutable."""
+return self._defaultvalue
+
 @abc.abstractmethod
 def newstate(self, oldstate, newparam, abort):
 """Adds newparam to oldstate and returns the new state.
 
 On failure, abort can be called with a string error message."""
 
 class _simpleopt(customopt):
 def _isboolopt(self):
-return isinstance(self.defaultvalue, (bool, type(None)))
+return isinstance(self._defaultvalue, (bool, type(None)))
 
 def newstate(self, oldstate, newparam, abort):
 return newparam
@@ -235,6 +242,9 @@
 return self.callablefn(newparam)
 
 class _listopt(customopt):
+def getdefaultvalue(self):
+return self._defaultvalue[:]
+
 def newstate(self, oldstate, newparam, abort):
 oldstate.append(newparam)
 return oldstate
@@ -313,7 +323,7 @@
 defmap[name] = _defaultopt(default)
 
 # copy defaults to state
-state[name] = defmap[name].defaultvalue
+state[name] = defmap[name].getdefaultvalue()
 
 # does it take a parameter?
 if not defmap[name]._isboolopt():



To: dploch, #hg-reviewers, yuja
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2935: fancyopts: fix rendering of customopt defaults in help text

2018-03-23 Thread dploch (Daniel Ploch)
dploch added a comment.


  In https://phab.mercurial-scm.org/D2935#47329, @yuja wrote:
  
  > Looks good, but can you split this to two patches?
  >
  > https://www.mercurial-scm.org/wiki/ContributingChanges#Submission_checklist
  
  
  Done, sorry.
  
  getdefaultvalue() change is now in https://phab.mercurial-scm.org/D2937

REPOSITORY
  rHG Mercurial

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

To: dploch, #hg-reviewers, yuja
Cc: yuja, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2935: fancyopts: fix rendering of customopt defaults in help text

2018-03-23 Thread dploch (Daniel Ploch)
dploch updated this revision to Diff 7257.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2935?vs=7255=7257

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

AFFECTED FILES
  mercurial/help.py

CHANGE DETAILS

diff --git a/mercurial/help.py b/mercurial/help.py
--- a/mercurial/help.py
+++ b/mercurial/help.py
@@ -20,6 +20,7 @@
 encoding,
 error,
 extensions,
+fancyopts,
 filemerge,
 fileset,
 minirst,
@@ -84,7 +85,10 @@
 if shortopt:
 so = '-' + shortopt
 lo = '--' + longopt
-if default:
+
+if isinstance(default, fancyopts.customopt):
+default = default.defaultvalue
+if default and not callable(default):
 # default is of unknown type, and in Python 2 we abused
 # the %s-shows-repr property to handle integers etc. To
 # match that behavior on Python 3, we do str(default) and



To: dploch, #hg-reviewers, yuja
Cc: yuja, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2937: fancyopts: prevent mutation of the default value in customopts

2018-03-23 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  mercurial/fancyopts.py
  mercurial/help.py
  tests/test-help.t

CHANGE DETAILS

diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -716,15 +716,23 @@
 
   $ cat > helpext.py < import os
-  > from mercurial import commands, registrar
+  > from mercurial import commands, fancyopts, registrar
   > 
+  > def func(arg):
+  > return '%sfoo' % arg
+  > class customopt(fancyopts.customopt):
+  > def newstate(self, oldstate, newparam, abort):
+  > return '%sbar' % oldstate
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > 
   > @command(b'nohelp',
-  > [(b'', b'longdesc', 3, b'x'*90),
+  > [(b'', b'longdesc', 3, b'x'*67),
   > (b'n', b'', None, b'normal desc'),
-  > (b'', b'newline', b'', b'line1\nline2')],
+  > (b'', b'newline', b'', b'line1\nline2'),
+  > (b'', b'callableopt', func, b'adds foo'),
+  > (b'', b'customopt', customopt(''), b'adds bar'),
+  > (b'', b'customopt-withdefault', customopt('foo'), b'adds bar')],
   > b'hg nohelp',
   > norepo=True)
   > @command(b'debugoptADV', [(b'', b'aopt', None, b'option is (ADVANCED)')])
@@ -786,10 +794,14 @@
   
   options:
   
-  --longdesc VALUE 
x
-   x (default: 3)
-   -n --   normal desc
-  --newline VALUE  line1 line2
+  --longdesc VALUE
+

+xxx (default: 3)
+   -n --normal desc
+  --newline VALUE   line1 line2
+  --callableopt VALUE   adds foo
+  --customopt VALUE adds bar
+  --customopt-withdefault VALUE adds bar (default: foo)
   
   (some details hidden, use --verbose to show complete help)
 
diff --git a/mercurial/help.py b/mercurial/help.py
--- a/mercurial/help.py
+++ b/mercurial/help.py
@@ -87,7 +87,7 @@
 lo = '--' + longopt
 
 if isinstance(default, fancyopts.customopt):
-default = default.defaultvalue
+default = default.getdefaultvalue()
 if default and not callable(default):
 # default is of unknown type, and in Python 2 we abused
 # the %s-shows-repr property to handle integers etc. To
diff --git a/mercurial/fancyopts.py b/mercurial/fancyopts.py
--- a/mercurial/fancyopts.py
+++ b/mercurial/fancyopts.py
@@ -208,20 +208,27 @@
 __metaclass__ = abc.ABCMeta
 
 def __init__(self, defaultvalue):
-self.defaultvalue = defaultvalue
+self._defaultvalue = defaultvalue
 
 def _isboolopt(self):
 return False
 
+def getdefaultvalue(self):
+"""Returns the default value for this opt.
+
+Subclasses should override this to return a new value if the value type
+is mutable."""
+return self._defaultvalue
+
 @abc.abstractmethod
 def newstate(self, oldstate, newparam, abort):
 """Adds newparam to oldstate and returns the new state.
 
 On failure, abort can be called with a string error message."""
 
 class _simpleopt(customopt):
 def _isboolopt(self):
-return isinstance(self.defaultvalue, (bool, type(None)))
+return isinstance(self._defaultvalue, (bool, type(None)))
 
 def newstate(self, oldstate, newparam, abort):
 return newparam
@@ -235,6 +242,9 @@
 return self.callablefn(newparam)
 
 class _listopt(customopt):
+def getdefaultvalue(self):
+return self._defaultvalue[:]
+
 def newstate(self, oldstate, newparam, abort):
 oldstate.append(newparam)
 return oldstate
@@ -313,7 +323,7 @@
 defmap[name] = _defaultopt(default)
 
 # copy defaults to state
-state[name] = defmap[name].defaultvalue
+state[name] = defmap[name].getdefaultvalue()
 
 # does it take a parameter?
 if not defmap[name]._isboolopt():



To: dploch, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2935: fancyopts: fix rendering of customopt defaults in help text

2018-03-22 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Also make a getdefaultvalue() function to prevent unwanted mutation.

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  mercurial/fancyopts.py
  mercurial/help.py
  tests/test-help.t

CHANGE DETAILS

diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -716,15 +716,23 @@
 
   $ cat > helpext.py < import os
-  > from mercurial import commands, registrar
+  > from mercurial import commands, fancyopts, registrar
   > 
+  > def func(arg):
+  > return '%sfoo' % arg
+  > class customopt(fancyopts.customopt):
+  > def newstate(self, oldstate, newparam, abort):
+  > return '%sbar' % oldstate
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > 
   > @command(b'nohelp',
-  > [(b'', b'longdesc', 3, b'x'*90),
+  > [(b'', b'longdesc', 3, b'x'*67),
   > (b'n', b'', None, b'normal desc'),
-  > (b'', b'newline', b'', b'line1\nline2')],
+  > (b'', b'newline', b'', b'line1\nline2'),
+  > (b'', b'callableopt', func, b'adds foo'),
+  > (b'', b'customopt', customopt(''), b'adds bar'),
+  > (b'', b'customopt-withdefault', customopt('foo'), b'adds bar')],
   > b'hg nohelp',
   > norepo=True)
   > @command(b'debugoptADV', [(b'', b'aopt', None, b'option is (ADVANCED)')])
@@ -786,10 +794,14 @@
   
   options:
   
-  --longdesc VALUE 
x
-   x (default: 3)
-   -n --   normal desc
-  --newline VALUE  line1 line2
+  --longdesc VALUE
+

+xxx (default: 3)
+   -n --normal desc
+  --newline VALUE   line1 line2
+  --callableopt VALUE   adds foo
+  --customopt VALUE adds bar
+  --customopt-withdefault VALUE adds bar (default: foo)
   
   (some details hidden, use --verbose to show complete help)
 
diff --git a/mercurial/help.py b/mercurial/help.py
--- a/mercurial/help.py
+++ b/mercurial/help.py
@@ -20,6 +20,7 @@
 encoding,
 error,
 extensions,
+fancyopts,
 filemerge,
 fileset,
 minirst,
@@ -84,7 +85,10 @@
 if shortopt:
 so = '-' + shortopt
 lo = '--' + longopt
-if default:
+
+if isinstance(default, fancyopts.customopt):
+default = default.getdefaultvalue()
+if default and not callable(default):
 # default is of unknown type, and in Python 2 we abused
 # the %s-shows-repr property to handle integers etc. To
 # match that behavior on Python 3, we do str(default) and
diff --git a/mercurial/fancyopts.py b/mercurial/fancyopts.py
--- a/mercurial/fancyopts.py
+++ b/mercurial/fancyopts.py
@@ -208,20 +208,27 @@
 __metaclass__ = abc.ABCMeta
 
 def __init__(self, defaultvalue):
-self.defaultvalue = defaultvalue
+self._defaultvalue = defaultvalue
 
 def _isboolopt(self):
 return False
 
+def getdefaultvalue(self):
+"""Returns the default value for this opt.
+
+Subclasses should override this to return a new value if the value type
+is mutable."""
+return self._defaultvalue
+
 @abc.abstractmethod
 def newstate(self, oldstate, newparam, abort):
 """Adds newparam to oldstate and returns the new state.
 
 On failure, abort can be called with a string error message."""
 
 class _simpleopt(customopt):
 def _isboolopt(self):
-return isinstance(self.defaultvalue, (bool, type(None)))
+return isinstance(self._defaultvalue, (bool, type(None)))
 
 def newstate(self, oldstate, newparam, abort):
 return newparam
@@ -235,6 +242,9 @@
 return self.callablefn(newparam)
 
 class _listopt(customopt):
+def getdefaultvalue(self):
+return self._defaultvalue[:]
+
 def newstate(self, oldstate, newparam, abort):
 oldstate.append(newparam)
 return oldstate
@@ -313,7 +323,7 @@
 defmap[name] = _defaultopt(default)
 
 # copy defaults to state
-state[name] = defmap[name].defaultvalue
+state[name] = defmap[name].getdefaultvalue()
 
 # does it take a parameter?
 if not defmap[name]._isboolopt():



To: dploch, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2090: fancyopts: add support for custom multi-arg opts in fancyopts.py

2018-02-21 Thread dploch (Daniel Ploch)
This revision was automatically updated to reflect the committed changes.
dploch marked 2 inline comments as done.
Closed by commit rHG2ed36fec5321: fancyopts: add support for custom multi-arg 
opts in fancyopts.py (authored by dploch, committed by ).

CHANGED PRIOR TO COMMIT
  https://phab.mercurial-scm.org/D2090?vs=5981=5984#toc

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2090?vs=5981=5984

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

AFFECTED FILES
  mercurial/fancyopts.py

CHANGE DETAILS

diff --git a/mercurial/fancyopts.py b/mercurial/fancyopts.py
--- a/mercurial/fancyopts.py
+++ b/mercurial/fancyopts.py
@@ -7,7 +7,9 @@
 
 from __future__ import absolute_import
 
+import abc
 import functools
+import types
 
 from .i18n import _
 from . import (
@@ -201,6 +203,64 @@
 parsedargs.extend(args[pos:])
 return parsedopts, parsedargs
 
+class customopt(object):
+"""Manage defaults and mutations for any type of opt."""
+
+__metaclass__ = abc.ABCMeta
+
+def __init__(self, defaultvalue):
+self.defaultvalue = defaultvalue
+
+def _isboolopt(self):
+return False
+
+@abc.abstractmethod
+def newstate(self, oldstate, newparam, abort):
+"""Adds newparam to oldstate and returns the new state.
+
+On failure, abort can be called with a string error message."""
+
+class _simpleopt(customopt):
+def _isboolopt(self):
+return isinstance(self.defaultvalue, (bool, types.NoneType))
+
+def newstate(self, oldstate, newparam, abort):
+return newparam
+
+class _callableopt(customopt):
+def __init__(self, callablefn):
+self.callablefn = callablefn
+super(_callableopt, self).__init__(None)
+
+def newstate(self, oldstate, newparam, abort):
+return self.callablefn(newparam)
+
+class _listopt(customopt):
+def newstate(self, oldstate, newparam, abort):
+oldstate.append(newparam)
+return oldstate
+
+class _intopt(customopt):
+def newstate(self, oldstate, newparam, abort):
+try:
+return int(newparam)
+except ValueError:
+abort(_('expected int'))
+
+def _defaultopt(default):
+"""Returns a default opt implementation, given a default value."""
+
+if isinstance(default, customopt):
+return default
+elif callable(default):
+return _callableopt(default)
+elif isinstance(default, list):
+return _listopt(default[:])
+elif type(default) is type(1):
+return _intopt(default)
+else:
+return _simpleopt(default)
+
 def fancyopts(args, options, state, gnu=False, early=False, optaliases=None):
 """
 read args, parse options, and store options in state
@@ -220,6 +280,7 @@
   list - parameter string is added to a list
   integer - parameter strings is stored as int
   function - call function with parameter
+  customopt - subclass of 'customopt'
 
 optaliases is a mapping from a canonical option name to a list of
 additional long options. This exists for preserving backward compatibility
@@ -250,18 +311,13 @@
 argmap['-' + short] = name
 for n in onames:
 argmap['--' + n] = name
-defmap[name] = default
+defmap[name] = _defaultopt(default)
 
 # copy defaults to state
-if isinstance(default, list):
-state[name] = default[:]
-elif callable(default):
-state[name] = None
-else:
-state[name] = default
+state[name] = defmap[name].defaultvalue
 
 # does it take a parameter?
-if not (default is None or default is True or default is False):
+if not defmap[name]._isboolopt():
 if short:
 short += ':'
 onames = [n + '=' for n in onames]
@@ -301,21 +357,13 @@
 boolval = False
 name = argmap[opt]
 obj = defmap[name]
-t = type(obj)
-if callable(obj):
-state[name] = defmap[name](val)
-elif t is type(1):
-try:
-state[name] = int(val)
-except ValueError:
-raise error.Abort(_('invalid value %r for option %s, '
-   'expected int') % (val, opt))
-elif t is type(''):
-state[name] = val
-elif t is type([]):
-state[name].append(val)
-elif t is type(None) or t is type(False):
+if obj._isboolopt():
 state[name] = boolval
+else:
+def abort(s):
+raise error.Abort(
+_('invalid value %r for option %s, %s') % (val, opt, s))
+state[name] = defmap[name].newstate(state[name], val, abort)
 
 # return unparsed args
 return args



To: dploch, #hg-reviewers, durin42, indygreg
Cc: durin42, indygreg, mercurial-devel
___
Mercurial-devel mailing list

D2090: fancyopts: add support for custom multi-arg opts in fancyopts.py

2018-02-21 Thread dploch (Daniel Ploch)
dploch added a comment.


  In https://phab.mercurial-scm.org/D2090#38775, @indygreg wrote:
  
  > Out of curiosity, do you think it would be possible to implement an option 
that behaved like a boolean when given in isolation but also optionally 
accepted a value? My use case is I want `hg serve --open` to automatically open 
a web browser pointing at the started server and `hg serve --open chrome` to 
open Chrome instead of my default web browser. I'm not sure if that's a good 
idea to implement in the parser though. It could possibly lead to ambiguous 
argument parsing.
  
  
  It feels like a bad idea.  If we want the parsing to be unambiguous, then 
'--open' must always come at the end of the command line if the default option 
is desired.  If there were a separate flag with the same semantics, you 
couldn't specify both:  'hg serve --open --foo' would pass "--foo" as the 
argument to --open.
  
  If we stipulate that the optional argument only counts as an argument if it 
doesn't look like a flag (^-(-)?[^\d-].*$), it sort of works, but that feels 
pretty messy.

INLINE COMMENTS

> indygreg wrote in fancyopts.py:258-261
> `isinstance()` is preferred here. Although the integer check could be a bit 
> wonky if Python 2's `long` ever comes into play.

It's actually wonkier than expected... :)

> indygreg wrote in fancyopts.py:313-317
> Oh, your code was copied from here. I suppose it is mostly OK then. Although 
> bringing this into modernity as part of the refactor wouldn't hurt...

Sure, modernized.

REPOSITORY
  rHG Mercurial

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

To: dploch, #hg-reviewers, durin42, indygreg
Cc: durin42, indygreg, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2090: fancyopts: add support for custom multi-arg opts in fancyopts.py

2018-02-21 Thread dploch (Daniel Ploch)
dploch updated this revision to Diff 5981.
dploch marked 4 inline comments as done.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2090?vs=5330=5981

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

AFFECTED FILES
  mercurial/fancyopts.py

CHANGE DETAILS

diff --git a/mercurial/fancyopts.py b/mercurial/fancyopts.py
--- a/mercurial/fancyopts.py
+++ b/mercurial/fancyopts.py
@@ -7,7 +7,9 @@
 
 from __future__ import absolute_import
 
+import abc
 import functools
+import types
 
 from .i18n import _
 from . import (
@@ -201,6 +203,65 @@
 parsedargs.extend(args[pos:])
 return parsedopts, parsedargs
 
+class customopt(object):
+"""Manage defaults and mutations for any type of opt."""
+
+__metaclass__ = abc.ABCMeta
+
+def __init__(self, defaultvalue):
+self.defaultvalue = defaultvalue
+
+def _isboolopt(self):
+return False
+
+@abc.abstractmethod
+def newstate(self, oldstate, newparam, abort):
+"""Adds newparam to oldstate and returns the new state.
+
+On failure, abort can be called with a string error message."""
+
+class _simpleopt(customopt):
+def _isboolopt(self):
+return isinstance(self.defaultvalue, (bool, types.NoneType))
+
+def newstate(self, oldstate, newparam, abort):
+return newparam
+
+class _callableopt(customopt):
+def __init__(self, callablefn):
+self.callablefn = callablefn
+super(_callableopt, self).__init__(None)
+
+def newstate(self, oldstate, newparam, abort):
+return self.callablefn(newparam)
+
+class _listopt(customopt):
+def newstate(self, oldstate, newparam, abort):
+oldstate.append(newparam)
+return oldstate
+
+class _intopt(customopt):
+def newstate(self, oldstate, newparam, abort):
+try:
+return int(newparam)
+except ValueError:
+abort(_('expected int'))
+
+def _defaultopt(default):
+"""Returns a default opt implementation, given a default value."""
+
+if isinstance(default, customopt):
+return default
+elif callable(default):
+return _callableopt(default)
+elif isinstance(default, list):
+return _listopt(default[:])
+# isinstance(True, int) returns True for some reason.
+elif isinstance(default, (int, long)) and not isinstance(default, bool):
+return _intopt(default)
+else:
+return _simpleopt(default)
+
 def fancyopts(args, options, state, gnu=False, early=False, optaliases=None):
 """
 read args, parse options, and store options in state
@@ -220,6 +281,7 @@
   list - parameter string is added to a list
   integer - parameter strings is stored as int
   function - call function with parameter
+  customopt - subclass of 'customopt'
 
 optaliases is a mapping from a canonical option name to a list of
 additional long options. This exists for preserving backward compatibility
@@ -250,18 +312,13 @@
 argmap['-' + short] = name
 for n in onames:
 argmap['--' + n] = name
-defmap[name] = default
+defmap[name] = _defaultopt(default)
 
 # copy defaults to state
-if isinstance(default, list):
-state[name] = default[:]
-elif callable(default):
-state[name] = None
-else:
-state[name] = default
+state[name] = defmap[name].defaultvalue
 
 # does it take a parameter?
-if not (default is None or default is True or default is False):
+if not defmap[name]._isboolopt():
 if short:
 short += ':'
 onames = [n + '=' for n in onames]
@@ -301,21 +358,13 @@
 boolval = False
 name = argmap[opt]
 obj = defmap[name]
-t = type(obj)
-if callable(obj):
-state[name] = defmap[name](val)
-elif t is type(1):
-try:
-state[name] = int(val)
-except ValueError:
-raise error.Abort(_('invalid value %r for option %s, '
-   'expected int') % (val, opt))
-elif t is type(''):
-state[name] = val
-elif t is type([]):
-state[name].append(val)
-elif t is type(None) or t is type(False):
+if obj._isboolopt():
 state[name] = boolval
+else:
+def abort(s):
+raise error.Abort(
+_('invalid value %r for option %s, %s') % (val, opt, s))
+state[name] = defmap[name].newstate(state[name], val, abort)
 
 # return unparsed args
 return args



To: dploch, #hg-reviewers, durin42, indygreg
Cc: durin42, indygreg, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2090: fancyopts: add support for custom multi-arg opts in fancyopts.py

2018-02-20 Thread dploch (Daniel Ploch)
dploch added a comment.


  Friendly ping!  This is my first commit so I'm not sure if more information 
or changes are expected; please let me know if there's anything I'm missing.

REPOSITORY
  rHG Mercurial

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

To: dploch, #hg-reviewers, durin42
Cc: durin42, indygreg, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2090: fancyopts: add support for custom multi-arg opts in fancyopts.py

2018-02-07 Thread dploch (Daniel Ploch)
dploch added a comment.


  In https://phab.mercurial-scm.org/D2090#34906, @indygreg wrote:
  
  > The fancyopts code is some of the oldest in Mercurial. We've been wanting 
to rewrite it for a while. This patch seems like an interesting and more 
powerful direction to take the parser.
  >
  > Out of curiosity, do you have an intended use case in mind? Will that use 
case be in Mercurial itself or is this for an extension?
  
  
  I didn't have a use case for Mercurial itself in mind, but I wouldn't be 
surprised if there was one.  My intended use case is the 'csv' flag example in 
the commit description: We have a lot of flags in our internal extensions that 
require the ["alice,bob", "charlie"] -> ["alice", "bob", "charlie"] behavior, 
so it would be really nice to be able to declare a shareable customopt for 
these instead of needing to individually wrap every applicable `opts['flag']` 
lookup.

REPOSITORY
  rHG Mercurial

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

To: dploch, #hg-reviewers
Cc: indygreg, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2090: fancyopts: add support for custom multi-arg opts in fancyopts.py

2018-02-07 Thread dploch (Daniel Ploch)
dploch created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This allows for more complex multi-arg opt logic, such as "--sum 1 --sum 2" 
-> 3, "--csv alice,bob --csv charlie" -> ["alice","bob","charlie"].  The 
current support for callables is insufficient for this.
  
  This is done by introducing a 'customopt' class which can be extended for 
more powerful opts logic.  All existing opt-types are converted to use this 
class, simplifying the fancyopts() logic.

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  mercurial/fancyopts.py

CHANGE DETAILS

diff --git a/mercurial/fancyopts.py b/mercurial/fancyopts.py
--- a/mercurial/fancyopts.py
+++ b/mercurial/fancyopts.py
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import abc
 import functools
 
 from .i18n import _
@@ -201,6 +202,66 @@
 parsedargs.extend(args[pos:])
 return parsedopts, parsedargs
 
+class customopt(object):
+"""Manage defaults and mutations for any type of opt."""
+
+__metaclass__ = abc.ABCMeta
+
+def __init__(self, defaultvalue):
+self.defaultvalue = defaultvalue
+
+def _isboolopt(self):
+return False
+
+@abc.abstractmethod
+def newstate(self, oldstate, newparam, abort):
+"""Adds newparam to oldstate and returns the new state.
+
+On failure, abort can be called with a string error message."""
+
+class _simpleopt(customopt):
+def _isboolopt(self):
+t = type(self.defaultvalue)
+return t is type(False) or t is type(None)
+
+def newstate(self, oldstate, newparam, abort):
+return newparam
+
+class _callableopt(customopt):
+def __init__(self, callable):
+self.callable = callable
+super(_callableopt, self).__init__(None)
+
+def newstate(self, oldstate, newparam, abort):
+return self.callable(newparam)
+
+class _listopt(customopt):
+def newstate(self, oldstate, newparam, abort):
+oldstate.append(newparam)
+return oldstate
+
+class _intopt(customopt):
+def newstate(self, oldstate, newparam, abort):
+try:
+return int(newparam)
+except ValueError:
+abort(_('expected int'))
+
+def _defaultopt(default):
+"""Returns a default opt implementation, given a default value."""
+
+t = type(default)
+if isinstance(default, customopt):
+return default
+elif callable(default):
+return _callableopt(default)
+elif t is type([]):
+return _listopt(default[:])
+elif t is type(1):
+return _intopt(default)
+else:
+return _simpleopt(default)
+
 def fancyopts(args, options, state, gnu=False, early=False, optaliases=None):
 """
 read args, parse options, and store options in state
@@ -220,6 +281,7 @@
   list - parameter string is added to a list
   integer - parameter strings is stored as int
   function - call function with parameter
+  customopt - subclass of 'customopt'
 
 optaliases is a mapping from a canonical option name to a list of
 additional long options. This exists for preserving backward compatibility
@@ -250,18 +312,13 @@
 argmap['-' + short] = name
 for n in onames:
 argmap['--' + n] = name
-defmap[name] = default
+defmap[name] = _defaultopt(default)
 
 # copy defaults to state
-if isinstance(default, list):
-state[name] = default[:]
-elif callable(default):
-state[name] = None
-else:
-state[name] = default
+state[name] = defmap[name].defaultvalue
 
 # does it take a parameter?
-if not (default is None or default is True or default is False):
+if not defmap[name]._isboolopt():
 if short:
 short += ':'
 onames = [n + '=' for n in onames]
@@ -301,21 +358,13 @@
 boolval = False
 name = argmap[opt]
 obj = defmap[name]
-t = type(obj)
-if callable(obj):
-state[name] = defmap[name](val)
-elif t is type(1):
-try:
-state[name] = int(val)
-except ValueError:
-raise error.Abort(_('invalid value %r for option %s, '
-   'expected int') % (val, opt))
-elif t is type(''):
-state[name] = val
-elif t is type([]):
-state[name].append(val)
-elif t is type(None) or t is type(False):
+if obj._isboolopt():
 state[name] = boolval
+else:
+def abort(s):
+raise error.Abort(
+_('invalid value %r for option %s, %s') % (val, opt, s))
+state[name] = defmap[name].newstate(state[name], val, abort)
 
 # return unparsed args
 return args



To: dploch, #hg-reviewers
Cc: mercurial-devel