mercurial/state.py
changeset 45171 5322e738be0f
parent 43773 7b14d649af1b
child 45215 a253ded5b03d
equal deleted inserted replaced
45170:c87bd1fe3da2 45171:5322e738be0f
    16 We store the data on disk in cbor, for which we use the CBOR format to encode
    16 We store the data on disk in cbor, for which we use the CBOR format to encode
    17 the data.
    17 the data.
    18 """
    18 """
    19 
    19 
    20 from __future__ import absolute_import
    20 from __future__ import absolute_import
       
    21 
       
    22 import contextlib
    21 
    23 
    22 from .i18n import _
    24 from .i18n import _
    23 
    25 
    24 from . import (
    26 from . import (
    25     error,
    27     error,
   117         clearable,
   119         clearable,
   118         allowcommit,
   120         allowcommit,
   119         reportonly,
   121         reportonly,
   120         continueflag,
   122         continueflag,
   121         stopflag,
   123         stopflag,
       
   124         childopnames,
   122         cmdmsg,
   125         cmdmsg,
   123         cmdhint,
   126         cmdhint,
   124         statushint,
   127         statushint,
   125         abortfunc,
   128         abortfunc,
   126         continuefunc,
   129         continuefunc,
   130         self._clearable = clearable
   133         self._clearable = clearable
   131         self._allowcommit = allowcommit
   134         self._allowcommit = allowcommit
   132         self._reportonly = reportonly
   135         self._reportonly = reportonly
   133         self._continueflag = continueflag
   136         self._continueflag = continueflag
   134         self._stopflag = stopflag
   137         self._stopflag = stopflag
       
   138         self._childopnames = childopnames
       
   139         self._delegating = False
   135         self._cmdmsg = cmdmsg
   140         self._cmdmsg = cmdmsg
   136         self._cmdhint = cmdhint
   141         self._cmdhint = cmdhint
   137         self._statushint = statushint
   142         self._statushint = statushint
   138         self.abortfunc = abortfunc
   143         self.abortfunc = abortfunc
   139         self.continuefunc = continuefunc
   144         self.continuefunc = continuefunc
   179         """determines whether a multi-step operation is in progress
   184         """determines whether a multi-step operation is in progress
   180         or not
   185         or not
   181         """
   186         """
   182         if self._opname == b'merge':
   187         if self._opname == b'merge':
   183             return len(repo[None].parents()) > 1
   188             return len(repo[None].parents()) > 1
       
   189         elif self._delegating:
       
   190             return False
   184         else:
   191         else:
   185             return repo.vfs.exists(self._fname)
   192             return repo.vfs.exists(self._fname)
   186 
   193 
   187 
   194 
   188 # A list of statecheck objects for multistep operations like graft.
   195 # A list of statecheck objects for multistep operations like graft.
   189 _unfinishedstates = []
   196 _unfinishedstates = []
       
   197 _unfinishedstatesbyname = {}
   190 
   198 
   191 
   199 
   192 def addunfinished(
   200 def addunfinished(
   193     opname,
   201     opname,
   194     fname,
   202     fname,
   195     clearable=False,
   203     clearable=False,
   196     allowcommit=False,
   204     allowcommit=False,
   197     reportonly=False,
   205     reportonly=False,
   198     continueflag=False,
   206     continueflag=False,
   199     stopflag=False,
   207     stopflag=False,
       
   208     childopnames=None,
   200     cmdmsg=b"",
   209     cmdmsg=b"",
   201     cmdhint=b"",
   210     cmdhint=b"",
   202     statushint=b"",
   211     statushint=b"",
   203     abortfunc=None,
   212     abortfunc=None,
   204     continuefunc=None,
   213     continuefunc=None,
   216     need to detect the operation using 'hg status --verbose'
   225     need to detect the operation using 'hg status --verbose'
   217     continueflag is a boolean determines whether or not a command supports
   226     continueflag is a boolean determines whether or not a command supports
   218     `--continue` option or not.
   227     `--continue` option or not.
   219     stopflag is a boolean that determines whether or not a command supports
   228     stopflag is a boolean that determines whether or not a command supports
   220     --stop flag
   229     --stop flag
       
   230     childopnames is a list of other opnames this op uses as sub-steps of its
       
   231     own execution. They must already be added.
   221     cmdmsg is used to pass a different status message in case standard
   232     cmdmsg is used to pass a different status message in case standard
   222     message of the format "abort: cmdname in progress" is not desired.
   233     message of the format "abort: cmdname in progress" is not desired.
   223     cmdhint is used to pass a different hint message in case standard
   234     cmdhint is used to pass a different hint message in case standard
   224     message of the format "To continue: hg cmdname --continue
   235     message of the format "To continue: hg cmdname --continue
   225     To abort: hg cmdname --abort" is not desired.
   236     To abort: hg cmdname --abort" is not desired.
   228     'To abort:       hg cmdname --abort') is not desired
   239     'To abort:       hg cmdname --abort') is not desired
   229     abortfunc stores the function required to abort an unfinished state.
   240     abortfunc stores the function required to abort an unfinished state.
   230     continuefunc stores the function required to finish an interrupted
   241     continuefunc stores the function required to finish an interrupted
   231     operation.
   242     operation.
   232     """
   243     """
       
   244     childopnames = childopnames or []
   233     statecheckobj = _statecheck(
   245     statecheckobj = _statecheck(
   234         opname,
   246         opname,
   235         fname,
   247         fname,
   236         clearable,
   248         clearable,
   237         allowcommit,
   249         allowcommit,
   238         reportonly,
   250         reportonly,
   239         continueflag,
   251         continueflag,
   240         stopflag,
   252         stopflag,
       
   253         childopnames,
   241         cmdmsg,
   254         cmdmsg,
   242         cmdhint,
   255         cmdhint,
   243         statushint,
   256         statushint,
   244         abortfunc,
   257         abortfunc,
   245         continuefunc,
   258         continuefunc,
   246     )
   259     )
       
   260 
   247     if opname == b'merge':
   261     if opname == b'merge':
   248         _unfinishedstates.append(statecheckobj)
   262         _unfinishedstates.append(statecheckobj)
   249     else:
   263     else:
       
   264         # This check enforces that for any op 'foo' which depends on op 'bar',
       
   265         # 'foo' comes before 'bar' in _unfinishedstates. This ensures that
       
   266         # getrepostate() always returns the most specific applicable answer.
       
   267         for childopname in childopnames:
       
   268             if childopname not in _unfinishedstatesbyname:
       
   269                 raise error.ProgrammingError(
       
   270                     _(b'op %s depends on unknown op %s') % (opname, childopname)
       
   271                 )
       
   272 
   250         _unfinishedstates.insert(0, statecheckobj)
   273         _unfinishedstates.insert(0, statecheckobj)
       
   274 
       
   275     if opname in _unfinishedstatesbyname:
       
   276         raise error.ProgrammingError(_(b'op %s registered twice') % opname)
       
   277     _unfinishedstatesbyname[opname] = statecheckobj
       
   278 
       
   279 
       
   280 def _getparentandchild(opname, childopname):
       
   281     p = _unfinishedstatesbyname.get(opname, None)
       
   282     if not p:
       
   283         raise error.ProgrammingError(_(b'unknown op %s') % opname)
       
   284     if childopname not in p._childopnames:
       
   285         raise error.ProgrammingError(
       
   286             _(b'op %s does not delegate to %s') % (opname, childopname)
       
   287         )
       
   288     c = _unfinishedstatesbyname[childopname]
       
   289     return p, c
       
   290 
       
   291 
       
   292 @contextlib.contextmanager
       
   293 def delegating(repo, opname, childopname):
       
   294     """context wrapper for delegations from opname to childopname.
       
   295 
       
   296     requires that childopname was specified when opname was registered.
       
   297 
       
   298     Usage:
       
   299       def my_command_foo_that_uses_rebase(...):
       
   300         ...
       
   301         with state.delegating(repo, 'foo', 'rebase'):
       
   302           _run_rebase(...)
       
   303         ...
       
   304     """
       
   305 
       
   306     p, c = _getparentandchild(opname, childopname)
       
   307     if p._delegating:
       
   308         raise error.ProgrammingError(
       
   309             _(b'cannot delegate from op %s recursively') % opname
       
   310         )
       
   311     p._delegating = True
       
   312     try:
       
   313         yield
       
   314     except error.ConflictResolutionRequired as e:
       
   315         # Rewrite conflict resolution advice for the parent opname.
       
   316         if e.opname == childopname:
       
   317             raise error.ConflictResolutionRequired(opname)
       
   318         raise e
       
   319     finally:
       
   320         p._delegating = False
       
   321 
       
   322 
       
   323 def ischildunfinished(repo, opname, childopname):
       
   324     """Returns true if both opname and childopname are unfinished."""
       
   325 
       
   326     p, c = _getparentandchild(opname, childopname)
       
   327     return (p._delegating or p.isunfinished(repo)) and c.isunfinished(repo)
       
   328 
       
   329 
       
   330 def continuechild(ui, repo, opname, childopname):
       
   331     """Checks that childopname is in progress, and continues it."""
       
   332 
       
   333     p, c = _getparentandchild(opname, childopname)
       
   334     if not ischildunfinished(repo, opname, childopname):
       
   335         raise error.ProgrammingError(
       
   336             _(b'child op %s of parent %s is not unfinished')
       
   337             % (childopname, opname)
       
   338         )
       
   339     if not c.continuefunc:
       
   340         raise error.ProgrammingError(
       
   341             _(b'op %s has no continue function') % childopname
       
   342         )
       
   343     return c.continuefunc(ui, repo)
   251 
   344 
   252 
   345 
   253 addunfinished(
   346 addunfinished(
   254     b'update',
   347     b'update',
   255     fname=b'updatestate',
   348     fname=b'updatestate',