[PATCH 3 of 3] extensions: add unwrapfunction to undo wrapfunction
Martijn Pieters
mj at zopatista.com
Fri Jul 1 08:41:51 EDT 2016
On 1 July 2016 at 13:09, Jun Wu <quark at fb.com> wrote:
> # HG changeset patch
> # User Jun Wu <quark at fb.com>
> # Date 1467374153 -3600
> # Fri Jul 01 12:55:53 2016 +0100
> # Node ID 7a68b01000d12e1795d142cdb32fdd62a5ffc8cb
> # Parent de798903374922317eb2cbca9733ba0cea415780
> # Available At https://bitbucket.org/quark-zju/hg-draft
> # hg pull https://bitbucket.org/quark-zju/hg-draft -r 7a68b01000d1
> extensions: add unwrapfunction to undo wrapfunction
>
> Before this patch, we don't have a safe way to undo a wrapfunction because
> other extensions may wrap the same function and calling setattr will undo
> them accidentally.
>
> This patch adds an "unwrapfunction" to address the issue. It removes the
> wrapper from the wrapper chain, and re-wraps everything, which is not the
> most efficient but short and easy to understand. We can revisit the code
> if we have perf issues with long chains.
>
> The "undo" feature is useful in cases like wrapping a function just in
> a scope. And it allows extensions running with chg to add and remove their
> wrappers dynamically in reposetup without starting a new chgserver.
>
> diff --git a/mercurial/extensions.py b/mercurial/extensions.py
> --- a/mercurial/extensions.py
> +++ b/mercurial/extensions.py
> @@ -303,6 +303,19 @@ def wrapfunction(container, funcname, wr
> setattr(container, funcname, wrap)
> return origfn
>
> +def unwrapfunction(container, funcname, wrapper):
> + '''Undo wrapfunction
> +
> + Pass the same arguments with wrapfunction to undo a wrap.
> + Handles stacked wrappers correctly.
> + '''
> + chain = getwrapperchain(container, funcname)
> + origfn = chain.pop()
> + chain.remove(wrapper)
> + setattr(container, funcname, origfn)
> + for wrapper in reversed(chain):
> + wrapfunction(container, funcname, wrapper)
> +
You could just use a stack of functions to rewrap; this avoids having
to re-do the whole chain:
chain = getwrapperchain(container, funcname)[::-1]
rewrap = []
while chain:
wrapper = chain.pop()
if wrapper == origfn:
break
rewrap.append(wrapper)
if chain:
# we found the original and removed it; rewrap whatever we had to remove
# to find it.
setattr(container, funcname, chain[-1])
while rewrap:
wrapfunction(container, funcname, rewrap.pop())
> def getwrapperchain(container, funcname):
> '''get a chain of wrappers of a function
>
> diff --git a/tests/test-extensions-wrapfunction.py b/tests/test-extensions-wrapfunction.py
> new file mode 100644
> --- /dev/null
> +++ b/tests/test-extensions-wrapfunction.py
> @@ -0,0 +1,24 @@
> +from __future__ import absolute_import, print_function
> +
> +from mercurial import extensions
> +
> +def genwrap(x):
> + def f(orig, *args, **kwds):
> + return [x] + orig(*args, **kwds)
> + return f
> +
> +wrappers = [genwrap(i) for i in range(5)]
> +
> +class dummyclass(object):
> + def foo(self):
> + return ['orig']
> +
> +dummy = dummyclass()
> +
> +for w in wrappers + [wrappers[0]]:
> + extensions.wrapfunction(dummy, 'foo', w)
> + print(dummy.foo())
> +
> +for i in [3, 0, 4, 0, 2, 1]:
> + extensions.unwrapfunction(dummy, 'foo', wrappers[i])
> + print(dummy.foo())
> diff --git a/tests/test-extensions-wrapfunction.py.out b/tests/test-extensions-wrapfunction.py.out
> new file mode 100644
> --- /dev/null
> +++ b/tests/test-extensions-wrapfunction.py.out
> @@ -0,0 +1,12 @@
> +[0, 'orig']
> +[1, 0, 'orig']
> +[2, 1, 0, 'orig']
> +[3, 2, 1, 0, 'orig']
> +[4, 3, 2, 1, 0, 'orig']
> +[0, 4, 3, 2, 1, 0, 'orig']
> +[0, 4, 2, 1, 0, 'orig']
> +[4, 2, 1, 0, 'orig']
> +[2, 1, 0, 'orig']
> +[2, 1, 'orig']
> +[1, 'orig']
> +['orig']
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
--
Martijn Pieters
More information about the Mercurial-devel
mailing list