deprecate: introduce ui.deprecate

Summary:
Adds a simple ui.deprecate() function for deprecating code paths. It
has several levels of deprecation and can optionally automatically escalate
through those levels across a specified time frame.

The levels are:
- Log - just logs the usage to scuba
- Warn - same as Log but also prints a warning to the user
- Slow - same as Warn but also sleeps for 2 seconds
- OptIn - throws an exception, but hints that the exception can be bypassed by
  setting 'deprecated.bypass-XXXX=True'
- Block - throws an exception that cannot be bypassed

I also call it from a few locations that are expected to be deprecated already.

Reviewed By: markbt

Differential Revision: D21890496

fbshipit-source-id: faddb301888ef75cc71d46ffb7374f3fe3d044bd
This commit is contained in:
Durham Goode 2020-06-08 16:18:49 -07:00 committed by Facebook GitHub Bot
parent e28bde4df3
commit 1d65457032
7 changed files with 157 additions and 1 deletions

View File

@ -125,8 +125,11 @@ class dirstate(object):
opener.makedirs("treestate") opener.makedirs("treestate")
self._mapcls = treestate.treestatemap self._mapcls = treestate.treestatemap
elif istreedirstate: elif istreedirstate:
ui.deprecate("treedirstate", "treedirstate is replaced by treestate")
self._mapcls = treedirstate.treedirstatemap self._mapcls = treedirstate.treedirstatemap
else: else:
if "eden" not in repo.requirements:
ui.deprecate("dirstatemap", "dirstatemap is replaced by treestate")
self._mapcls = dirstatemap self._mapcls = dirstatemap
self._fs = filesystem.physicalfilesystem(root, self) self._fs = filesystem.physicalfilesystem(root, self)

View File

@ -151,6 +151,10 @@ class Abort(Hint, Context, Component, Exception):
exitcode = 255 exitcode = 255
class DeprecatedError(Abort):
__bytes__ = _tobytes
class UncommitedChangesAbort(Abort): class UncommitedChangesAbort(Abort):
"""Raised if there are uncommited changs and the command requires a clean """Raised if there are uncommited changs and the command requires a clean
working copy working copy

View File

@ -934,30 +934,35 @@ def allsuccessors(obsstore, nodes, ignoreflags=0):
def marker(repo, data): def marker(repo, data):
movemsg = "obsolete.marker moved to obsutil.marker" movemsg = "obsolete.marker moved to obsutil.marker"
repo.ui.deprecwarn(movemsg, "4.3") repo.ui.deprecwarn(movemsg, "4.3")
repo.ui.deprecate("obsolete.marker", movemsg)
return obsutil.marker(repo, data) return obsutil.marker(repo, data)
def getmarkers(repo, nodes=None, exclusive=False): def getmarkers(repo, nodes=None, exclusive=False):
movemsg = "obsolete.getmarkers moved to obsutil.getmarkers" movemsg = "obsolete.getmarkers moved to obsutil.getmarkers"
repo.ui.deprecwarn(movemsg, "4.3") repo.ui.deprecwarn(movemsg, "4.3")
repo.ui.deprecate("obsolete.getmarkers", movemsg)
return obsutil.getmarkers(repo, nodes=nodes, exclusive=exclusive) return obsutil.getmarkers(repo, nodes=nodes, exclusive=exclusive)
def exclusivemarkers(repo, nodes): def exclusivemarkers(repo, nodes):
movemsg = "obsolete.exclusivemarkers moved to obsutil.exclusivemarkers" movemsg = "obsolete.exclusivemarkers moved to obsutil.exclusivemarkers"
repo.ui.deprecwarn(movemsg, "4.3") repo.ui.deprecwarn(movemsg, "4.3")
repo.ui.deprecate("obsolete.exclusivemarkers", movemsg)
return obsutil.exclusivemarkers(repo, nodes) return obsutil.exclusivemarkers(repo, nodes)
def foreground(repo, nodes): def foreground(repo, nodes):
movemsg = "obsolete.foreground moved to obsutil.foreground" movemsg = "obsolete.foreground moved to obsutil.foreground"
repo.ui.deprecwarn(movemsg, "4.3") repo.ui.deprecwarn(movemsg, "4.3")
repo.ui.deprecate("obsolete.foreground", movemsg)
return obsutil.foreground(repo, nodes) return obsutil.foreground(repo, nodes)
def successorssets(repo, initialnode, cache=None): def successorssets(repo, initialnode, cache=None):
movemsg = "obsolete.successorssets moved to obsutil.successorssets" movemsg = "obsolete.successorssets moved to obsutil.successorssets"
repo.ui.deprecwarn(movemsg, "4.3") repo.ui.deprecwarn(movemsg, "4.3")
repo.ui.deprecate("obsolete.successorssets", movemsg)
return obsutil.successorssets(repo, initialnode, cache=cache) return obsutil.successorssets(repo, initialnode, cache=cache)
@ -1049,6 +1054,7 @@ def _computeobsoleteset(repo):
def _computeunstableset(repo): def _computeunstableset(repo):
msg = "'unstable' volatile set is deprecated, " "use 'orphan'" msg = "'unstable' volatile set is deprecated, " "use 'orphan'"
repo.ui.deprecwarn(msg, "4.4") repo.ui.deprecwarn(msg, "4.4")
repo.ui.deprecate("unstable-obsolete-set", msg)
return _computeorphanset(repo) return _computeorphanset(repo)
@ -1088,6 +1094,7 @@ def _computeextinctset(repo):
def _computebumpedset(repo): def _computebumpedset(repo):
msg = "'bumped' volatile set is deprecated, " "use 'phasedivergent'" msg = "'bumped' volatile set is deprecated, " "use 'phasedivergent'"
repo.ui.deprecwarn(msg, "4.4") repo.ui.deprecwarn(msg, "4.4")
repo.ui.deprecate("obsolete.bumped", msg)
return _computephasedivergentset(repo) return _computephasedivergentset(repo)
@ -1121,6 +1128,7 @@ def _computephasedivergentset(repo):
def _computedivergentset(repo): def _computedivergentset(repo):
msg = "'divergent' volatile set is deprecated, " "use 'contentdivergent'" msg = "'divergent' volatile set is deprecated, " "use 'contentdivergent'"
repo.ui.deprecwarn(msg, "4.4") repo.ui.deprecwarn(msg, "4.4")
repo.ui.deprecate("obsolete.divergent", msg)
return _computecontentdivergentset(repo) return _computecontentdivergentset(repo)

View File

@ -681,6 +681,9 @@ def branch(repo, subset, x):
def bumped(repo, subset, x): def bumped(repo, subset, x):
msg = "'bumped()' is deprecated, " "use 'phasedivergent()'" msg = "'bumped()' is deprecated, " "use 'phasedivergent()'"
repo.ui.deprecwarn(msg, "4.4") repo.ui.deprecwarn(msg, "4.4")
repo.ui.deprecate(
"bumped-revset", "bumped() has been replaced with phasedivergent()"
)
return phasedivergent(repo, subset, x) return phasedivergent(repo, subset, x)
@ -929,6 +932,9 @@ def destination(repo, subset, x):
def divergent(repo, subset, x): def divergent(repo, subset, x):
msg = "'divergent()' is deprecated, " "use 'contentdivergent()'" msg = "'divergent()' is deprecated, " "use 'contentdivergent()'"
repo.ui.deprecwarn(msg, "4.4") repo.ui.deprecwarn(msg, "4.4")
repo.ui.deprecate(
"divergent-revset", "divergent() has been replaced with contentdivergent()"
)
return contentdivergent(repo, subset, x) return contentdivergent(repo, subset, x)
@ -2320,6 +2326,7 @@ def _substringmatcher(pattern, casesensitive=True):
def unstable(repo, subset, x): def unstable(repo, subset, x):
msg = "'unstable()' is deprecated, " "use 'orphan()'" msg = "'unstable()' is deprecated, " "use 'orphan()'"
repo.ui.deprecwarn(msg, "4.4") repo.ui.deprecwarn(msg, "4.4")
repo.ui.deprecate("unstable-revset", "unstable() has been replaced with orphan()")
return orphan(repo, subset, x) return orphan(repo, subset, x)

View File

@ -981,6 +981,9 @@ def showtroubles(repo, **args):
""" """
msg = "'troubles' is deprecated, " "use 'instabilities'" msg = "'troubles' is deprecated, " "use 'instabilities'"
repo.ui.deprecwarn(msg, "4.4") repo.ui.deprecwarn(msg, "4.4")
repo.ui.deprecate(
"troubles-template", "troubles has been replaced with instabilities"
)
return showinstabilities(repo=repo, **args) return showinstabilities(repo=repo, **args)

View File

@ -26,7 +26,7 @@ import sys
import tempfile import tempfile
import time import time
import traceback import traceback
from enum import Enum from enum import IntEnum
from typing import Any, Dict, List, Optional, Tuple, Union from typing import Any, Dict, List, Optional, Tuple, Union
import bindings import bindings
@ -156,6 +156,19 @@ _unset = uiconfig._unset
_reqexithandlers = [] _reqexithandlers = []
class deprecationlevel(IntEnum):
# Logs usage of the deprecated code path
Log = 0
# Prints a warning on usage of the deprecated code path
Warn = 1
# Inserts a 2 second sleep to the deprecated code path
Slow = 2
# Throws an exception, but a config can be used to opt in to the deprecated feature
Optin = 3
# Throws a non-bypassable exception
Block = 4
class ui(object): class ui(object):
def __init__(self, src=None): def __init__(self, src=None):
"""Create a fresh new ui object if no src given """Create a fresh new ui object if no src given
@ -1503,6 +1516,77 @@ class ui(object):
self._logsample(service, *origmsg, **opts) self._logsample(service, *origmsg, **opts)
def deprecate(
self, name, message, maxlevel=deprecationlevel.Log, startstr=None, endstr=None
):
"""marks a code path as deprecated
The default behavior is to simply log the usage of the deprecated path,
but `maxlevel` can be used to specify stricter deprecation strategies.
If `start` and `end` are provided, the deprecation level will be slowly
increased over the course of the `start` and `end` time, reaching the
specified `maxlevel` at the end time.
"""
level = maxlevel
if startstr is not None and endstr is not None:
now = time.time()
start = util.parsedate(startstr)[0]
end = util.parsedate(endstr)[0]
# Linearly interpolate to get the current level
percent = float(now - start) / float(end - start)
level = max(0, min(int(percent * maxlevel), maxlevel))
self.log(
"deprecated",
message,
feature=name,
level=int(level),
version=util.version(),
)
bypassed = self.configbool("deprecated", "bypass-%s" % name)
if level == deprecationlevel.Block:
raise error.DeprecatedError(
_("feature '%s' is disabled: %s") % (name, message)
)
elif level == deprecationlevel.Optin and not bypassed:
hint = (
_(
"set config `deprecated.bypass-%s=True` to temporarily bypass this block"
)
% name
)
if endstr is not None and maxlevel == deprecationlevel.Block:
hint = _(
"set config `deprecated.bypass-%s=True` to bypass this block, but note the feature will be completely disabled on %s"
) % (name, endstr)
raise error.DeprecatedError(
_("feature '%s' is disabled: %s") % (name, message), hint=hint
)
elif level >= deprecationlevel.Slow and not bypassed:
self.warn(
_(
"warning: sleeping for 2 seconds because feature '%s' is deprecated: %s\n"
)
% (name, message)
)
self.warn(
_(
"note: the feature will be completely disabled soon, so please migrate off\n"
)
)
time.sleep(2)
elif level >= deprecationlevel.Warn:
self.warn(_("warning: feature '%s' is deprecated: %s\n") % (name, message))
self.warn(
_(
"note: the feature will be completely disabled soon, so please migrate off\n"
)
)
else:
self.develwarn(_("feature '%s' is deprecated: %s\n") % (name, message))
def _computesamplingfilters(self): def _computesamplingfilters(self):
filtermap = {} filtermap = {}
for k in self.configitems("sampling"): for k in self.configitems("sampling"):

View File

@ -0,0 +1,47 @@
#chg-compatible
$ configure modern
$ newext deprecatecmd <<EOF
> from edenscm.mercurial import registrar
> cmdtable = {}
> command = registrar.command(cmdtable)
> @command('testdeprecate', [], 'hg testdeprecate')
> def testdeprecate(ui, repo, level):
> ui.deprecate("test-feature", "blah blah message", int(level))
> EOF
$ hg init client
$ cd client
$ hg testdeprecate 0
devel-warn: feature 'test-feature' is deprecated: blah blah message
at: $TESTTMP/deprecatecmd.py:6 (testdeprecate)
$ hg blackbox | grep deprecated
* [legacy][deprecated] blah blah message (glob)
* [legacy][develwarn] devel-warn: feature 'test-feature' is deprecated: blah blah message (glob)
$ hg testdeprecate 1
warning: feature 'test-feature' is deprecated: blah blah message
note: the feature will be completely disabled sooned, so please migrate off
$ hg testdeprecate 2
warning: sleeping for 2 seconds because feature 'test-feature' is deprecated: blah blah message
note: the feature will be completely disabled sooned, so please migrate off
$ hg testdeprecate 3
abort: feature 'test-feature' is disabled: blah blah message
(set config `deprecated.bypass-test-feature=True` to temporarily bypass this block)
[255]
$ hg testdeprecate 3 --config deprecated.bypass-test-feature=True
warning: feature 'test-feature' is deprecated: blah blah message
note: the feature will be completely disabled sooned, so please migrate off
$ hg testdeprecate 4
abort: feature 'test-feature' is disabled: blah blah message
[255]
$ hg testdeprecate 4 --config deprecated.bypass-test-feature=True
abort: feature 'test-feature' is disabled: blah blah message
[255]