[pypy-commit] pypy cpyext-more-slots: merge default into branch
mattip
pypy.commits at gmail.com
Sun May 8 15:32:41 EDT 2016
Author: Matti Picus <matti.picus at gmail.com>
Branch: cpyext-more-slots
Changeset: r84314:eef6d2175abb
Date: 2016-05-08 22:31 +0300
http://bitbucket.org/pypy/pypy/changeset/eef6d2175abb/
Log: merge default into branch
diff --git a/lib_pypy/cffi/api.py b/lib_pypy/cffi/api.py
--- a/lib_pypy/cffi/api.py
+++ b/lib_pypy/cffi/api.py
@@ -397,20 +397,7 @@
data. Later, when this new cdata object is garbage-collected,
'destructor(old_cdata_object)' will be called.
"""
- try:
- gcp = self._backend.gcp
- except AttributeError:
- pass
- else:
- return gcp(cdata, destructor)
- #
- with self._lock:
- try:
- gc_weakrefs = self.gc_weakrefs
- except AttributeError:
- from .gc_weakref import GcWeakrefs
- gc_weakrefs = self.gc_weakrefs = GcWeakrefs(self)
- return gc_weakrefs.build(cdata, destructor)
+ return self._backend.gcp(cdata, destructor)
def _get_cached_btype(self, type):
assert self._lock.acquire(False) is False
diff --git a/lib_pypy/cffi/backend_ctypes.py b/lib_pypy/cffi/backend_ctypes.py
--- a/lib_pypy/cffi/backend_ctypes.py
+++ b/lib_pypy/cffi/backend_ctypes.py
@@ -460,6 +460,11 @@
return x._value
raise TypeError("character expected, got %s" %
type(x).__name__)
+ def __nonzero__(self):
+ return ord(self._value) != 0
+ else:
+ def __nonzero__(self):
+ return self._value != 0
if kind == 'float':
@staticmethod
@@ -993,6 +998,31 @@
assert onerror is None # XXX not implemented
return BType(source, error)
+ def gcp(self, cdata, destructor):
+ BType = self.typeof(cdata)
+
+ if destructor is None:
+ if not (hasattr(BType, '_gcp_type') and
+ BType._gcp_type is BType):
+ raise TypeError("Can remove destructor only on a object "
+ "previously returned by ffi.gc()")
+ cdata._destructor = None
+ return None
+
+ try:
+ gcp_type = BType._gcp_type
+ except AttributeError:
+ class CTypesDataGcp(BType):
+ __slots__ = ['_orig', '_destructor']
+ def __del__(self):
+ if self._destructor is not None:
+ self._destructor(self._orig)
+ gcp_type = BType._gcp_type = CTypesDataGcp
+ new_cdata = self.cast(gcp_type, cdata)
+ new_cdata._orig = cdata
+ new_cdata._destructor = destructor
+ return new_cdata
+
typeof = type
def getcname(self, BType, replace_with):
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -79,3 +79,6 @@
It is a more flexible way to make RPython finalizers.
.. branch: unpacking-cpython-shortcut
+
+.. branch: cleanups
+
diff --git a/pypy/module/_multibytecodec/app_multibytecodec.py b/pypy/module/_multibytecodec/app_multibytecodec.py
--- a/pypy/module/_multibytecodec/app_multibytecodec.py
+++ b/pypy/module/_multibytecodec/app_multibytecodec.py
@@ -44,8 +44,10 @@
self, data))
def reset(self):
- self.stream.write(MultibyteIncrementalEncoder.encode(
- self, '', final=True))
+ data = MultibyteIncrementalEncoder.encode(
+ self, '', final=True)
+ if len(data) > 0:
+ self.stream.write(data)
MultibyteIncrementalEncoder.reset(self)
def writelines(self, lines):
diff --git a/pypy/module/_multibytecodec/test/test_app_stream.py b/pypy/module/_multibytecodec/test/test_app_stream.py
--- a/pypy/module/_multibytecodec/test/test_app_stream.py
+++ b/pypy/module/_multibytecodec/test/test_app_stream.py
@@ -90,3 +90,15 @@
w.write(u'\u304b')
w.write(u'\u309a')
assert w.stream.output == ['\x83m', '', '\x82\xf5']
+
+ def test_writer_seek_no_empty_write(self):
+ # issue #2293: codecs.py will sometimes issue a reset()
+ # on a StreamWriter attached to a file that is not opened
+ # for writing at all. We must not emit a "write('')"!
+ class FakeFile:
+ def write(self, data):
+ raise IOError("can't write!")
+ #
+ w = self.ShiftJisx0213StreamWriter(FakeFile())
+ w.reset()
+ # assert did not crash
diff --git a/pypy/module/micronumpy/concrete.py b/pypy/module/micronumpy/concrete.py
--- a/pypy/module/micronumpy/concrete.py
+++ b/pypy/module/micronumpy/concrete.py
@@ -258,7 +258,6 @@
elif space.is_w(w_idx, space.w_None):
return [NewAxisChunk(), EllipsisChunk()]
result = []
- i = 0
has_ellipsis = False
has_filter = False
for w_item in space.fixedview(w_idx):
@@ -274,7 +273,6 @@
result.append(NewAxisChunk())
elif space.isinstance_w(w_item, space.w_slice):
result.append(SliceChunk(w_item))
- i += 1
elif isinstance(w_item, W_NDimArray) and w_item.get_dtype().is_bool():
if has_filter:
# in CNumPy, the support for this is incomplete
@@ -287,7 +285,6 @@
result.append(IntegerChunk(w_item.descr_int(space)))
else:
result.append(IntegerChunk(w_item))
- i += 1
if not has_ellipsis:
result.append(EllipsisChunk())
return result
diff --git a/pypy/module/micronumpy/loop.py b/pypy/module/micronumpy/loop.py
--- a/pypy/module/micronumpy/loop.py
+++ b/pypy/module/micronumpy/loop.py
@@ -199,7 +199,7 @@
reds='auto')
def call_many_to_many(space, shape, func, in_dtypes, out_dtypes, in_args, out_args):
- # out must hav been built. func needs no calc_type, is usually an
+ # out must have been built. func needs no calc_type, is usually an
# external ufunc
nin = len(in_args)
in_iters = [None] * nin
@@ -806,7 +806,6 @@
indexlen = len(indexes_w)
dtype = arr.get_dtype()
iter = PureShapeIter(iter_shape, indexes_w)
- indexlen = len(indexes_w)
while not iter.done():
getitem_int_driver.jit_merge_point(shapelen=shapelen, indexlen=indexlen,
dtype=dtype, prefixlen=prefixlen)
diff --git a/pypy/module/micronumpy/ndarray.py b/pypy/module/micronumpy/ndarray.py
--- a/pypy/module/micronumpy/ndarray.py
+++ b/pypy/module/micronumpy/ndarray.py
@@ -231,11 +231,11 @@
dim = i
idx = c.w_idx
chunks.pop(i)
- chunks.insert(0, SliceChunk(space.newslice(space.wrap(0),
+ chunks.insert(0, SliceChunk(space.newslice(space.wrap(0),
space.w_None, space.w_None)))
break
if dim > 0:
- view = self.implementation.swapaxes(space, self, 0, dim)
+ view = self.implementation.swapaxes(space, self, 0, dim)
if dim >= 0:
view = new_view(space, self, chunks)
view.setitem_filter(space, idx, val_arr)
@@ -563,7 +563,7 @@
l_w = []
for i in range(self.get_shape()[0]):
item_w = self.descr_getitem(space, space.wrap(i))
- if (isinstance(item_w, W_NDimArray) or
+ if (isinstance(item_w, W_NDimArray) or
isinstance(item_w, boxes.W_GenericBox)):
l_w.append(space.call_method(item_w, "tolist"))
else:
@@ -740,7 +740,7 @@
space.str_w(self.get_dtype().descr_repr(space)),
space.str_w(new_dtype.descr_repr(space)), casting)
order = order_converter(space, space.wrap(order), self.get_order())
- if (not copy and new_dtype == self.get_dtype()
+ if (not copy and new_dtype == self.get_dtype()
and (order in (NPY.KEEPORDER, NPY.ANYORDER) or order == self.get_order())
and (subok or type(self) is W_NDimArray)):
return self
diff --git a/pypy/module/micronumpy/strides.py b/pypy/module/micronumpy/strides.py
--- a/pypy/module/micronumpy/strides.py
+++ b/pypy/module/micronumpy/strides.py
@@ -1,14 +1,13 @@
from pypy.interpreter.error import oefmt
from rpython.rlib import jit
-from pypy.module.micronumpy import support, constants as NPY
+from pypy.module.micronumpy import constants as NPY
from pypy.module.micronumpy.base import W_NDimArray
# structures to describe slicing
class BaseChunk(object):
- _attrs_ = ['step','out_dim']
- pass
+ _attrs_ = ['step', 'out_dim']
class Chunk(BaseChunk):
@@ -36,6 +35,7 @@
class IntegerChunk(BaseChunk):
input_dim = 1
out_dim = 0
+
def __init__(self, w_idx):
self.w_idx = w_idx
@@ -70,6 +70,7 @@
class EllipsisChunk(BaseChunk):
input_dim = 0
out_dim = 0
+
def __init__(self):
pass
@@ -80,6 +81,7 @@
class BooleanChunk(BaseChunk):
input_dim = 1
out_dim = 1
+
def __init__(self, w_idx):
self.w_idx = w_idx
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py
@@ -56,7 +56,7 @@
max = int(max)
p = ffi.cast(c_decl, min)
assert p != min # no __eq__(int)
- assert bool(p) is True
+ assert bool(p) is bool(min)
assert int(p) == min
p = ffi.cast(c_decl, max)
assert int(p) == max
@@ -285,7 +285,9 @@
assert ffi.new("char*", b"\xff")[0] == b'\xff'
assert ffi.new("char*")[0] == b'\x00'
assert int(ffi.cast("char", 300)) == 300 - 256
- assert bool(ffi.cast("char", 0))
+ assert not bool(ffi.cast("char", 0))
+ assert bool(ffi.cast("char", 1))
+ assert bool(ffi.cast("char", 255))
py.test.raises(TypeError, ffi.new, "char*", 32)
py.test.raises(TypeError, ffi.new, "char*", u+"x")
py.test.raises(TypeError, ffi.new, "char*", b"foo")
@@ -326,7 +328,11 @@
py.test.raises(TypeError, ffi.new, "wchar_t*", u+'\U00012345')
assert ffi.new("wchar_t*")[0] == u+'\x00'
assert int(ffi.cast("wchar_t", 300)) == 300
- assert bool(ffi.cast("wchar_t", 0))
+ assert not bool(ffi.cast("wchar_t", 0))
+ assert bool(ffi.cast("wchar_t", 1))
+ assert bool(ffi.cast("wchar_t", 65535))
+ if SIZE_OF_WCHAR > 2:
+ assert bool(ffi.cast("wchar_t", 65536))
py.test.raises(TypeError, ffi.new, "wchar_t*", 32)
py.test.raises(TypeError, ffi.new, "wchar_t*", "foo")
#
@@ -1523,21 +1529,30 @@
import gc; gc.collect(); gc.collect(); gc.collect()
assert seen == [3]
+ def test_gc_disable(self):
+ ffi = FFI(backend=self.Backend())
+ p = ffi.new("int *", 123)
+ py.test.raises(TypeError, ffi.gc, p, None)
+ seen = []
+ q1 = ffi.gc(p, lambda p: seen.append(1))
+ q2 = ffi.gc(q1, lambda p: seen.append(2))
+ import gc; gc.collect()
+ assert seen == []
+ assert ffi.gc(q1, None) is None
+ del q1, q2
+ import gc; gc.collect(); gc.collect(); gc.collect()
+ assert seen == [2]
+
def test_gc_finite_list(self):
ffi = FFI(backend=self.Backend())
- public = not hasattr(ffi._backend, 'gcp')
p = ffi.new("int *", 123)
keepalive = []
for i in range(10):
keepalive.append(ffi.gc(p, lambda p: None))
- if public:
- assert len(ffi.gc_weakrefs.data) == i + 1
del keepalive[:]
import gc; gc.collect(); gc.collect()
for i in range(10):
keepalive.append(ffi.gc(p, lambda p: None))
- if public:
- assert len(ffi.gc_weakrefs.data) == 10
def test_CData_CType(self):
ffi = FFI(backend=self.Backend())
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_ffi_backend.py b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_ffi_backend.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_ffi_backend.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_ffi_backend.py
@@ -467,12 +467,12 @@
def test_introspect_order(self):
ffi = FFI()
- ffi.cdef("union aaa { int a; }; typedef struct ccc { int a; } b;")
- ffi.cdef("union g { int a; }; typedef struct cc { int a; } bbb;")
- ffi.cdef("union aa { int a; }; typedef struct a { int a; } bb;")
- assert ffi.list_types() == (['b', 'bb', 'bbb'],
- ['a', 'cc', 'ccc'],
- ['aa', 'aaa', 'g'])
+ ffi.cdef("union CFFIaaa { int a; }; typedef struct CFFIccc { int a; } CFFIb;")
+ ffi.cdef("union CFFIg { int a; }; typedef struct CFFIcc { int a; } CFFIbbb;")
+ ffi.cdef("union CFFIaa { int a; }; typedef struct CFFIa { int a; } CFFIbb;")
+ assert ffi.list_types() == (['CFFIb', 'CFFIbb', 'CFFIbbb'],
+ ['CFFIa', 'CFFIcc', 'CFFIccc'],
+ ['CFFIaa', 'CFFIaaa', 'CFFIg'])
def test_unpack(self):
ffi = FFI()
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_new_ffi_1.py b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_new_ffi_1.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_new_ffi_1.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_new_ffi_1.py
@@ -139,7 +139,7 @@
max = int(max)
p = ffi.cast(c_decl, min)
assert p != min # no __eq__(int)
- assert bool(p) is True
+ assert bool(p) is bool(min)
assert int(p) == min
p = ffi.cast(c_decl, max)
assert int(p) == max
@@ -351,7 +351,9 @@
assert ffi.new("char*", b"\xff")[0] == b'\xff'
assert ffi.new("char*")[0] == b'\x00'
assert int(ffi.cast("char", 300)) == 300 - 256
- assert bool(ffi.cast("char", 0))
+ assert not bool(ffi.cast("char", 0))
+ assert bool(ffi.cast("char", 1))
+ assert bool(ffi.cast("char", 255))
py.test.raises(TypeError, ffi.new, "char*", 32)
py.test.raises(TypeError, ffi.new, "char*", u+"x")
py.test.raises(TypeError, ffi.new, "char*", b"foo")
@@ -391,7 +393,11 @@
py.test.raises(TypeError, ffi.new, "wchar_t*", u+'\U00012345')
assert ffi.new("wchar_t*")[0] == u+'\x00'
assert int(ffi.cast("wchar_t", 300)) == 300
- assert bool(ffi.cast("wchar_t", 0))
+ assert not bool(ffi.cast("wchar_t", 0))
+ assert bool(ffi.cast("wchar_t", 1))
+ assert bool(ffi.cast("wchar_t", 65535))
+ if SIZE_OF_WCHAR > 2:
+ assert bool(ffi.cast("wchar_t", 65536))
py.test.raises(TypeError, ffi.new, "wchar_t*", 32)
py.test.raises(TypeError, ffi.new, "wchar_t*", "foo")
#
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_recompiler.py
@@ -1898,14 +1898,14 @@
def test_introspect_order():
ffi = FFI()
- ffi.cdef("union aaa { int a; }; typedef struct ccc { int a; } b;")
- ffi.cdef("union g { int a; }; typedef struct cc { int a; } bbb;")
- ffi.cdef("union aa { int a; }; typedef struct a { int a; } bb;")
+ ffi.cdef("union CFFIaaa { int a; }; typedef struct CFFIccc { int a; } CFFIb;")
+ ffi.cdef("union CFFIg { int a; }; typedef struct CFFIcc { int a; } CFFIbbb;")
+ ffi.cdef("union CFFIaa { int a; }; typedef struct CFFIa { int a; } CFFIbb;")
verify(ffi, "test_introspect_order", """
- union aaa { int a; }; typedef struct ccc { int a; } b;
- union g { int a; }; typedef struct cc { int a; } bbb;
- union aa { int a; }; typedef struct a { int a; } bb;
+ union CFFIaaa { int a; }; typedef struct CFFIccc { int a; } CFFIb;
+ union CFFIg { int a; }; typedef struct CFFIcc { int a; } CFFIbbb;
+ union CFFIaa { int a; }; typedef struct CFFIa { int a; } CFFIbb;
""")
- assert ffi.list_types() == (['b', 'bb', 'bbb'],
- ['a', 'cc', 'ccc'],
- ['aa', 'aaa', 'g'])
+ assert ffi.list_types() == (['CFFIb', 'CFFIbb', 'CFFIbbb'],
+ ['CFFIa', 'CFFIcc', 'CFFIccc'],
+ ['CFFIaa', 'CFFIaaa', 'CFFIg'])
diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_zdist.py b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_zdist.py
--- a/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_zdist.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/cffi1/test_zdist.py
@@ -280,6 +280,14 @@
pass
with open("setup.py", "w") as f:
f.write("""if 1:
+ # https://bugs.python.org/issue23246
+ import sys
+ if sys.platform == 'win32':
+ try:
+ import setuptools
+ except ImportError:
+ pass
+
import cffi
ffi = cffi.FFI()
ffi.set_source("pack1.mymod", "/*code would be here*/")
diff --git a/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py
--- a/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/embedding/test_basic.py
@@ -80,8 +80,21 @@
# find a solution to that: we could hack sys.path inside the
# script run here, but we can't hack it in the same way in
# execute().
- output = self._run([sys.executable,
- os.path.join(local_dir, filename)])
+ pathname = os.path.join(path, filename)
+ with open(pathname, 'w') as g:
+ g.write('''
+# https://bugs.python.org/issue23246
+import sys
+if sys.platform == 'win32':
+ try:
+ import setuptools
+ except ImportError:
+ pass
+''')
+ with open(os.path.join(local_dir, filename), 'r') as f:
+ g.write(f.read())
+
+ output = self._run([sys.executable, pathname])
match = re.compile(r"\bFILENAME: (.+)").search(output)
assert match
dynamic_lib_name = match.group(1)
diff --git a/pypy/module/test_lib_pypy/cffi_tests/udir.py b/pypy/module/test_lib_pypy/cffi_tests/udir.py
--- a/pypy/module/test_lib_pypy/cffi_tests/udir.py
+++ b/pypy/module/test_lib_pypy/cffi_tests/udir.py
@@ -1,4 +1,14 @@
# Generated by pypy/tool/import_cffi.py
import py
+import sys
udir = py.path.local.make_numbered_dir(prefix = 'ffi-')
+
+
+# Windows-only workaround for some configurations: see
+# https://bugs.python.org/issue23246 (Python 2.7.9)
+if sys.platform == 'win32':
+ try:
+ import setuptools
+ except ImportError:
+ pass
diff --git a/rpython/jit/metainterp/optimizeopt/intutils.py b/rpython/jit/metainterp/optimizeopt/intutils.py
--- a/rpython/jit/metainterp/optimizeopt/intutils.py
+++ b/rpython/jit/metainterp/optimizeopt/intutils.py
@@ -1,5 +1,8 @@
+import sys
from rpython.rlib.rarithmetic import ovfcheck, LONG_BIT, maxint, is_valid_int
from rpython.rlib.objectmodel import we_are_translated
+from rpython.rtyper.lltypesystem import lltype
+from rpython.rtyper.lltypesystem.lloperation import llop
from rpython.jit.metainterp.resoperation import rop, ResOperation
from rpython.jit.metainterp.optimizeopt.info import AbstractInfo, INFO_NONNULL,\
INFO_UNKNOWN, INFO_NULL
@@ -174,15 +177,13 @@
def div_bound(self, other):
if self.has_upper and self.has_lower and \
other.has_upper and other.has_lower and \
- not other.contains(0):
- try:
- vals = (ovfcheck(self.upper / other.upper),
- ovfcheck(self.upper / other.lower),
- ovfcheck(self.lower / other.upper),
- ovfcheck(self.lower / other.lower))
- return IntBound(min4(vals), max4(vals))
- except OverflowError:
- return IntUnbounded()
+ not other.contains(0) and self.lower > (-sys.maxint-1):
+ vals = (
+ llop.int_floordiv(lltype.Signed, self.upper, other.upper),
+ llop.int_floordiv(lltype.Signed, self.upper, other.lower),
+ llop.int_floordiv(lltype.Signed, self.lower, other.upper),
+ llop.int_floordiv(lltype.Signed, self.lower, other.lower))
+ return IntBound(min4(vals), max4(vals))
else:
return IntUnbounded()
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_intbound.py b/rpython/jit/metainterp/optimizeopt/test/test_intbound.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_intbound.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_intbound.py
@@ -240,6 +240,8 @@
def test_div_bound():
+ from rpython.rtyper.lltypesystem import lltype
+ from rpython.rtyper.lltypesystem.lloperation import llop
for _, _, b1 in some_bounds():
for _, _, b2 in some_bounds():
b3 = b1.div_bound(b2)
@@ -247,7 +249,8 @@
for n2 in nbr:
if b1.contains(n1) and b2.contains(n2):
if n2 != 0:
- assert b3.contains(n1 / n2)
+ assert b3.contains(
+ llop.int_floordiv(lltype.Signed, n1, n2))
a=bound(2, 4).div_bound(bound(1, 2))
assert not a.contains(0)
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py
@@ -5529,6 +5529,27 @@
"""
self.optimize_loop(ops, expected)
+ def test_division_bound_bug(self):
+ ops = """
+ [i4]
+ i1 = int_ge(i4, -50)
+ guard_true(i1) []
+ i2 = int_le(i4, -40)
+ guard_true(i2) []
+ # here, -50 <= i4 <= -40
+
+ i5 = int_floordiv(i4, 30)
+ # here, we know that that i5 == -1 (C-style handling of negatives!)
+ escape_n(i5)
+ jump(i4)
+ """
+ expected = """
+ [i4, i5]
+ escape_n(-1)
+ jump(i4, -1)
+ """
+ self.optimize_loop(ops, expected)
+
def test_subsub_ovf(self):
ops = """
[i0]
More information about the pypy-commit
mailing list