[git] GPGME - branch, master, updated. gpgme-1.6.0-144-g3915842

by Justus Winter cvs at cvs.gnupg.org
Fri May 27 17:26:48 CEST 2016


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GnuPG Made Easy".

The branch, master has been updated
       via  3915842657f0849a038752fd7445f96081a89dd9 (commit)
       via  5265017d58e72b99b953932da2683522bb7bc741 (commit)
       via  e3dfd0a5ed53043a2fb1ceb23fc3aff03c47708d (commit)
       via  2ae847c02731994d99e69d3d025ff01f41406452 (commit)
       via  ebfe2300c33a3bad311e9ac1530e6c92636a08a4 (commit)
       via  e74cd9fb80f12b764d5e4561e73d55644147e9e7 (commit)
       via  bf188e280b8b4fc775f33c47e2e1e275ed044004 (commit)
       via  ce73ae9d0cbf782cd3a1949fc4f568f0d1da60d9 (commit)
      from  00ff6d07330028da370c869e3ec442eb76f8cbb8 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 3915842657f0849a038752fd7445f96081a89dd9
Author: Justus Winter <justus at gnupg.org>
Date:   Fri May 27 15:58:23 2016 +0200

    python: Port more tests.
    
    * lang/python/pyme/core.py (Data._error_check): Add
    'gpgme_data_get_file_name' to the list of functions not returning an
    error code.
    * lang/python/tests/Makefile.am (pytests): Add new tests.
    * lang/python/tests/support.py (verbose): New variable.
    * lang/python/tests/t-data.py: Test setting and getting the filename.
    * lang/python/tests/t-encrypt-large.py: New file.
    * lang/python/tests/t-file-name.py: Likewise.
    * lang/python/tests/t-trustlist.py: Likewise.
    
    Signed-off-by: Justus Winter <justus at gnupg.org>

diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index e89c181..cc262c9 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -363,11 +363,12 @@ class Data(GpgmeWrapper):
 
     def _errorcheck(self, name):
         """This function should list all functions returning gpgme_error_t"""
-        if name == 'gpgme_data_release_and_get_mem' or \
-               name == 'gpgme_data_get_encoding' or \
-               name == 'gpgme_data_seek':
-            return 0
-        return 1
+        return name not in {
+            'gpgme_data_release_and_get_mem',
+            'gpgme_data_get_encoding',
+            'gpgme_data_seek',
+            'gpgme_data_get_file_name',
+        }
 
     def __init__(self, string=None, file=None, offset=None,
                  length=None, cbs=None, copy=True):
diff --git a/lang/python/tests/Makefile.am b/lang/python/tests/Makefile.am
index 7df40a2..0bc8c7f 100644
--- a/lang/python/tests/Makefile.am
+++ b/lang/python/tests/Makefile.am
@@ -39,8 +39,11 @@ py_tests = t-wrapper.py \
 	t-signers.py \
 	t-decrypt.py \
 	t-export.py \
+	t-trustlist.py \
 	t-edit.py \
-	t-wait.py
+	t-wait.py \
+	t-encrypt-large.py \
+	t-file-name.py
 
 TESTS = $(top_srcdir)/tests/gpg/initial.test \
 	$(py_tests) \
diff --git a/lang/python/tests/support.py b/lang/python/tests/support.py
index a57d581..99d96cf 100644
--- a/lang/python/tests/support.py
+++ b/lang/python/tests/support.py
@@ -26,7 +26,8 @@ def init_gpgme(proto):
     core.check_version()
     core.engine_check_version(proto)
 
+verbose = int(os.environ.get('verbose', 0)) > 1
 def print_data(data):
-    if int(os.environ.get('verbose', 0)) > 1:
+    if verbose:
         data.seek(0, os.SEEK_SET)
         sys.stdout.buffer.write(data.read())
diff --git a/lang/python/tests/t-data.py b/lang/python/tests/t-data.py
index de60c47..3774f09 100755
--- a/lang/python/tests/t-data.py
+++ b/lang/python/tests/t-data.py
@@ -52,6 +52,10 @@ data.write(binjunk)
 data.seek(0, os.SEEK_SET)
 assert data.read() == binjunk
 
+data = core.Data()
+data.set_file_name("foobar")
+assert data.get_file_name() == "foobar"
+
 # Test reading from an existing file.
 with tempfile.NamedTemporaryFile() as tmp:
     tmp.write(binjunk)
diff --git a/lang/python/tests/t-encrypt-large.py b/lang/python/tests/t-encrypt-large.py
new file mode 100755
index 0000000..69aed48
--- /dev/null
+++ b/lang/python/tests/t-encrypt-large.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2016 g10 Code GmbH
+#
+# This file is part of GPGME.
+#
+# GPGME is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GPGME is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import random
+from pyme import core, constants
+import support
+
+if len(sys.argv) == 2:
+    nbytes = int(sys.argv[1])
+else:
+    nbytes = 100000
+
+support.init_gpgme(constants.PROTOCOL_OpenPGP)
+c = core.Context()
+
+ntoread = nbytes
+def read_cb(amount):
+    global ntoread
+    chunk = ntoread if ntoread < amount else amount
+    ntoread -= chunk
+    assert ntoread >= 0
+    assert chunk >= 0
+    return bytes(random.randrange(256) for i in range(chunk))
+
+nwritten = 0
+def write_cb(data):
+    global nwritten
+    nwritten += len(data)
+    return len(data)
+
+source = core.Data(cbs=(read_cb, None, None, lambda: None))
+sink = core.Data(cbs=(None, write_cb, None, lambda: None))
+
+keys = []
+keys.append(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False))
+keys.append(c.get_key("D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", False))
+
+c.op_encrypt(keys, constants.ENCRYPT_ALWAYS_TRUST, source, sink)
+result = c.op_encrypt_result()
+assert not result.invalid_recipients, \
+    "Invalid recipient encountered: {}".format(result.invalid_recipients.fpr)
+assert ntoread == 0
+
+if support.verbose:
+    sys.stderr.write(
+        "plaintext={} bytes, ciphertext={} bytes\n".format(nbytes, nwritten))
diff --git a/lang/python/tests/support.py b/lang/python/tests/t-file-name.py
old mode 100644
new mode 100755
similarity index 55%
copy from lang/python/tests/support.py
copy to lang/python/tests/t-file-name.py
index a57d581..6f9294e
--- a/lang/python/tests/support.py
+++ b/lang/python/tests/t-file-name.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python3
+
 # Copyright (C) 2016 g10 Code GmbH
 #
 # This file is part of GPGME.
@@ -15,18 +17,26 @@
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
-import sys
 import os
-from pyme import core
+from pyme import core, constants
+import support
+
+testname = "abcde12345"
+
+support.init_gpgme(constants.PROTOCOL_OpenPGP)
+c = core.Context()
+c.set_armor(True)
 
-def make_filename(name):
-    return os.path.join(os.environ['top_srcdir'], 'tests', 'gpg', name)
+source = core.Data("Hallo Leute\n")
+source.set_file_name(testname)
+cipher = core.Data()
+plain = core.Data()
 
-def init_gpgme(proto):
-    core.check_version()
-    core.engine_check_version(proto)
+keys = []
+keys.append(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False))
 
-def print_data(data):
-    if int(os.environ.get('verbose', 0)) > 1:
-        data.seek(0, os.SEEK_SET)
-        sys.stdout.buffer.write(data.read())
+c.op_encrypt(keys, constants.ENCRYPT_ALWAYS_TRUST, source, cipher)
+cipher.seek(0, os.SEEK_SET)
+c.op_decrypt(cipher, plain)
+result = c.op_decrypt_result()
+assert result.file_name == testname
diff --git a/lang/python/tests/support.py b/lang/python/tests/t-trustlist.py
old mode 100644
new mode 100755
similarity index 64%
copy from lang/python/tests/support.py
copy to lang/python/tests/t-trustlist.py
index a57d581..61f8fa5
--- a/lang/python/tests/support.py
+++ b/lang/python/tests/t-trustlist.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python3
+
 # Copyright (C) 2016 g10 Code GmbH
 #
 # This file is part of GPGME.
@@ -15,18 +17,18 @@
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
-import sys
-import os
-from pyme import core
+from pyme import core, constants
+import support
 
-def make_filename(name):
-    return os.path.join(os.environ['top_srcdir'], 'tests', 'gpg', name)
+support.init_gpgme(constants.PROTOCOL_OpenPGP)
+c = core.Context()
+c.op_trustlist_start("alice", 0)
 
-def init_gpgme(proto):
-    core.check_version()
-    core.engine_check_version(proto)
+while True:
+    item = c.op_trustlist_next()
+    if not item:
+        break
 
-def print_data(data):
-    if int(os.environ.get('verbose', 0)) > 1:
-        data.seek(0, os.SEEK_SET)
-        sys.stdout.buffer.write(data.read())
+    print("l={} k={} t={} o={} v={} u={}".format(
+        item.level, item.keyid, item.type, item.owner_trust,
+        item.validity, item.name))

commit 5265017d58e72b99b953932da2683522bb7bc741
Author: Justus Winter <justus at gnupg.org>
Date:   Fri May 27 15:46:17 2016 +0200

    src: Fix typo.
    
    --
    Signed-off-by: Justus Winter <justus at gnupg.org>

diff --git a/src/gpgme.h.in b/src/gpgme.h.in
index 87550fe..d68372c 100644
--- a/src/gpgme.h.in
+++ b/src/gpgme.h.in
@@ -1290,7 +1290,7 @@ gpgme_error_t gpgme_data_new_with_read_cb (gpgme_data_t *r_dh,
 
 /* Create a new data buffer filled with the content of file FNAME.
    COPY must be non-zero.  For delayed read, please use
-   gpgme_data_new_from_fd or gpgme_data_new_from stream instead.  */
+   gpgme_data_new_from_fd or gpgme_data_new_from_stream instead.  */
 gpgme_error_t gpgme_data_new_from_file (gpgme_data_t *r_dh,
 					const char *fname,
 					int copy);

commit e3dfd0a5ed53043a2fb1ceb23fc3aff03c47708d
Author: Justus Winter <justus at gnupg.org>
Date:   Fri May 27 15:27:55 2016 +0200

    tests: Fix test.
    
    --
    Signed-off-by: Justus Winter <justus at gnupg.org>

diff --git a/tests/gpg/t-file-name.c b/tests/gpg/t-file-name.c
index eb20fc0..dad4bd9 100644
--- a/tests/gpg/t-file-name.c
+++ b/tests/gpg/t-file-name.c
@@ -87,7 +87,7 @@ main (int argc, char *argv[])
     {
       fprintf (stderr, "%s:%i: Unexpected result file name: %s\n",
                __FILE__, __LINE__,
-	       result->file_name ? "(null)" : result->file_name);
+	       result->file_name ? result->file_name : "(null)");
       exit (1);
     }
 

commit 2ae847c02731994d99e69d3d025ff01f41406452
Author: Justus Winter <justus at gnupg.org>
Date:   Fri May 27 14:04:28 2016 +0200

    python: Implement data callbacks.
    
    * lang/python/gpgme.i (object_to_gpgme_t): Set exception on error.
    * lang/python/helpers.c (pyDataReadCb): New function.
    (pyDataWriteCb): Likewise.
    (pyDataSeekCb): Likewise.
    (pyDataReleaseCb): Likewise.
    (pygpgme_data_new_from_cbs): Likewise.
    * lang/python/helpers.h (pygpgme_data_new_from_cbs): New prototype.
    * lang/python/pyme/core.py (Data.__init__): Fix docstring, fix read
    callbacks.
    (Data.__del__): Fix read callbacks.
    (Data._free_readcb): Drop function.
    (Data._free_datacbs): New function.
    (Data.new_from_cbs): Fix setting the callbacks.
    (Data.write): Raise stashed exceptions.
    (Data.read): Likewise.
    * lang/python/tests/t-callbacks.py: Test new functionality.
    * lang/python/tests/t-data.py: Likewise.
    
    Signed-off-by: Justus Winter <justus at gnupg.org>

diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i
index 84bc6e9..5b3c193 100644
--- a/lang/python/gpgme.i
+++ b/lang/python/gpgme.i
@@ -180,10 +180,13 @@ PyObject* object_to_gpgme_t(PyObject* input, const char* objtype, int argnum) {
 			gpgme_data_t pubkey, gpgme_data_t seckey,
 			gpgme_data_t out};
 
-// SWIG has problem interpreting ssize_t, off_t or gpgme_error_t in gpgme.h
+/* SWIG has problems interpreting ssize_t, off_t or gpgme_error_t in
+   gpgme.h.  */
+/* XXX: This is wrong at least for off_t if compiled with LFS.  */
 %typemap(out) ssize_t, off_t, gpgme_error_t, gpgme_err_code_t, gpgme_err_source_t, gpg_error_t {
   $result = PyLong_FromLong($1);
 }
+/* XXX: This is wrong at least for off_t if compiled with LFS.  */
 %typemap(in) ssize_t, off_t, gpgme_error_t, gpgme_err_code_t, gpgme_err_source_t, gpg_error_t {
   $1 = PyLong_AsLong($input);
 }
@@ -201,7 +204,7 @@ PyObject* object_to_gpgme_t(PyObject* input, const char* objtype, int argnum) {
   Py_XDECREF($result);   /* Blow away any previous result */
   if (result < 0) {      /* Check for I/O error */
     free($1);
-    return NULL;
+    return PyErr_SetFromErrno(PyExc_RuntimeError);
   }
   $result = PyBytes_FromStringAndSize($1,result);
   free($1);
diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index 9fe81c9..4792c87 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -402,3 +402,245 @@ gpgme_error_t pyEditCb(void *opaque, gpgme_status_code_t status,
   Py_XDECREF(retval);
   return err_status;
 }
+

+/* Data callbacks.  */
+
+/* Read up to SIZE bytes into buffer BUFFER from the data object with
+   the handle HOOK.  Return the number of characters read, 0 on EOF
+   and -1 on error.  If an error occurs, errno is set.  */
+static ssize_t pyDataReadCb(void *hook, void *buffer, size_t size)
+{
+  ssize_t result;
+  PyObject *pyhook = (PyObject *) hook;
+  PyObject *self = NULL;
+  PyObject *func = NULL;
+  PyObject *dataarg = NULL;
+  PyObject *pyargs = NULL;
+  PyObject *retval = NULL;
+
+  assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
+
+  self = PyTuple_GetItem(pyhook, 0);
+  func = PyTuple_GetItem(pyhook, 1);
+  if (PyTuple_Size(pyhook) == 6) {
+    dataarg = PyTuple_GetItem(pyhook, 5);
+    pyargs = PyTuple_New(2);
+  } else {
+    pyargs = PyTuple_New(1);
+  }
+
+  PyTuple_SetItem(pyargs, 0, PyLong_FromSize_t(size));
+  if (dataarg) {
+    Py_INCREF(dataarg);
+    PyTuple_SetItem(pyargs, 1, dataarg);
+  }
+
+  retval = PyObject_CallObject(func, pyargs);
+  Py_DECREF(pyargs);
+  if (PyErr_Occurred()) {
+    pygpgme_stash_callback_exception(self);
+    result = -1;
+    goto leave;
+  }
+
+  if (! PyBytes_Check(retval)) {
+    PyErr_Format(PyExc_TypeError,
+                 "expected bytes from read callback, got %s",
+                 retval->ob_type->tp_name);
+    pygpgme_stash_callback_exception(self);
+    result = -1;
+    goto leave;
+  }
+
+  if (PyBytes_Size(retval) > size) {
+    PyErr_Format(PyExc_TypeError,
+                 "expected %zu bytes from read callback, got %zu",
+                 size, PyBytes_Size(retval));
+    pygpgme_stash_callback_exception(self);
+    result = -1;
+    goto leave;
+  }
+
+  memcpy(buffer, PyBytes_AsString(retval), PyBytes_Size(retval));
+  result = PyBytes_Size(retval);
+
+ leave:
+  Py_XDECREF(retval);
+  return result;
+}
+
+/* Write up to SIZE bytes from buffer BUFFER to the data object with
+   the handle HOOK.  Return the number of characters written, or -1
+   on error.  If an error occurs, errno is set.  */
+static ssize_t pyDataWriteCb(void *hook, const void *buffer, size_t size)
+{
+  ssize_t result;
+  PyObject *pyhook = (PyObject *) hook;
+  PyObject *self = NULL;
+  PyObject *func = NULL;
+  PyObject *dataarg = NULL;
+  PyObject *pyargs = NULL;
+  PyObject *retval = NULL;
+
+  assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
+
+  self = PyTuple_GetItem(pyhook, 0);
+  func = PyTuple_GetItem(pyhook, 2);
+  if (PyTuple_Size(pyhook) == 6) {
+    dataarg = PyTuple_GetItem(pyhook, 5);
+    pyargs = PyTuple_New(2);
+  } else {
+    pyargs = PyTuple_New(1);
+  }
+
+  PyTuple_SetItem(pyargs, 0, PyBytes_FromStringAndSize(buffer, size));
+  if (dataarg) {
+    Py_INCREF(dataarg);
+    PyTuple_SetItem(pyargs, 1, dataarg);
+  }
+
+  retval = PyObject_CallObject(func, pyargs);
+  Py_DECREF(pyargs);
+  if (PyErr_Occurred()) {
+    pygpgme_stash_callback_exception(self);
+    result = -1;
+    goto leave;
+  }
+
+  if (! PyLong_Check(retval)) {
+    PyErr_Format(PyExc_TypeError,
+                 "expected int from read callback, got %s",
+                 retval->ob_type->tp_name);
+    pygpgme_stash_callback_exception(self);
+    result = -1;
+    goto leave;
+  }
+
+  result = PyLong_AsSsize_t(retval);
+
+ leave:
+  Py_XDECREF(retval);
+  return result;
+}
+
+/* Set the current position from where the next read or write starts
+   in the data object with the handle HOOK to OFFSET, relativ to
+   WHENCE.  Returns the new offset in bytes from the beginning of the
+   data object.  */
+static off_t pyDataSeekCb(void *hook, off_t offset, int whence)
+{
+  off_t result;
+  PyObject *pyhook = (PyObject *) hook;
+  PyObject *self = NULL;
+  PyObject *func = NULL;
+  PyObject *dataarg = NULL;
+  PyObject *pyargs = NULL;
+  PyObject *retval = NULL;
+
+  assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
+
+  self = PyTuple_GetItem(pyhook, 0);
+  func = PyTuple_GetItem(pyhook, 3);
+  if (PyTuple_Size(pyhook) == 6) {
+    dataarg = PyTuple_GetItem(pyhook, 5);
+    pyargs = PyTuple_New(3);
+  } else {
+    pyargs = PyTuple_New(2);
+  }
+
+#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
+  PyTuple_SetItem(pyargs, 0, PyLong_FromLongLong((long long) offset));
+#else
+  PyTuple_SetItem(pyargs, 0, PyLong_FromLong((long) offset));
+#endif
+  PyTuple_SetItem(pyargs, 1, PyLong_FromLong((long) whence));
+  if (dataarg) {
+    Py_INCREF(dataarg);
+    PyTuple_SetItem(pyargs, 2, dataarg);
+  }
+
+  retval = PyObject_CallObject(func, pyargs);
+  Py_DECREF(pyargs);
+  if (PyErr_Occurred()) {
+    pygpgme_stash_callback_exception(self);
+    result = -1;
+    goto leave;
+  }
+
+  if (! PyLong_Check(retval)) {
+    PyErr_Format(PyExc_TypeError,
+                 "expected int from read callback, got %s",
+                 retval->ob_type->tp_name);
+    pygpgme_stash_callback_exception(self);
+    result = -1;
+    goto leave;
+  }
+
+#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
+  result = PyLong_AsLongLong(retval);
+#else
+  result = PyLong_AsLong(retval);
+#endif
+
+ leave:
+  Py_XDECREF(retval);
+  return result;
+}
+
+/* Close the data object with the handle HOOK.  */
+static void pyDataReleaseCb(void *hook)
+{
+  PyObject *pyhook = (PyObject *) hook;
+  PyObject *self = NULL;
+  PyObject *func = NULL;
+  PyObject *dataarg = NULL;
+  PyObject *pyargs = NULL;
+  PyObject *retval = NULL;
+
+  assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
+
+  self = PyTuple_GetItem(pyhook, 0);
+  func = PyTuple_GetItem(pyhook, 4);
+  if (PyTuple_Size(pyhook) == 6) {
+    dataarg = PyTuple_GetItem(pyhook, 5);
+    pyargs = PyTuple_New(1);
+  } else {
+    pyargs = PyTuple_New(0);
+  }
+
+  if (dataarg) {
+    Py_INCREF(dataarg);
+    PyTuple_SetItem(pyargs, 0, dataarg);
+  }
+
+  retval = PyObject_CallObject(func, pyargs);
+  Py_XDECREF(retval);
+  Py_DECREF(pyargs);
+  if (PyErr_Occurred())
+    pygpgme_stash_callback_exception(self);
+}
+
+gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
+                                        PyObject *pycbs,
+                                        PyObject **freelater)
+{
+  static struct gpgme_data_cbs cbs = {
+    pyDataReadCb,
+    pyDataWriteCb,
+    pyDataSeekCb,
+    pyDataReleaseCb,
+  };
+  PyObject *dataarg = NULL;
+
+  assert (PyTuple_Check(pycbs));
+  assert (PyTuple_Size(pycbs) == 5 || PyTuple_Size(pycbs) == 6);
+
+  Py_INCREF(pycbs);
+  *freelater = pycbs;
+
+  return gpgme_data_new_from_cbs(r_data, &cbs, (void *) pycbs);
+}
diff --git a/lang/python/helpers.h b/lang/python/helpers.h
index 5dd88a0..8b90008 100644
--- a/lang/python/helpers.h
+++ b/lang/python/helpers.h
@@ -40,3 +40,7 @@ void pygpgme_set_status_cb(gpgme_ctx_t ctx, PyObject *cb,
 
 gpgme_error_t pyEditCb(void *opaque, gpgme_status_code_t status,
 		       const char *args, int fd);
+
+gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
+                                        PyObject *pycbs,
+                                        PyObject **freelater);
diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index 6ef2dab..e89c181 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -384,7 +384,7 @@ class Data(GpgmeWrapper):
 
         If cbs is specified, it MUST be a tuple of the form:
 
-        ((read_cb, write_cb, seek_cb, release_cb), hook)
+        (read_cb, write_cb, seek_cb, release_cb[, hook])
 
         where func is a callback function taking two arguments (count,
         hook) and returning a string of read data, or None on EOF.
@@ -396,7 +396,7 @@ class Data(GpgmeWrapper):
 
         Any other use will result in undefined or erroneous behavior."""
         super().__init__(None)
-        self.last_readcb = None
+        self.data_cbs = None
 
         if cbs != None:
             self.new_from_cbs(*cbs)
@@ -419,15 +419,18 @@ class Data(GpgmeWrapper):
 
         if self.wrapped != None and pygpgme.gpgme_data_release:
             pygpgme.gpgme_data_release(self.wrapped)
-        self._free_readcb()
+            if self._callback_excinfo:
+                print(self._callback_excinfo)
+                pygpgme.pygpgme_raise_callback_exception(self)
+        self._free_datacbs()
 
-    def _free_readcb(self):
-        if self.last_readcb != None:
+    def _free_datacbs(self):
+        if self.data_cbs != None:
             if pygpgme.pygpgme_clear_generic_cb:
-                pygpgme.pygpgme_clear_generic_cb(self.last_readcb)
+                pygpgme.pygpgme_clear_generic_cb(self.data_cbs)
             if pygpgme.delete_PyObject_p_p:
-                pygpgme.delete_PyObject_p_p(self.last_readcb)
-            self.last_readcb = None
+                pygpgme.delete_PyObject_p_p(self.data_cbs)
+            self.data_cbs = None
 
     def new(self):
         tmp = pygpgme.new_gpgme_data_t_p()
@@ -453,14 +456,18 @@ class Data(GpgmeWrapper):
         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
         pygpgme.delete_gpgme_data_t_p(tmp)
 
-    def new_from_cbs(self, funcs, hook):
-        """Argument funcs must be a 4 element tuple with callbacks:
-        (read_cb, write_cb, seek_cb, release_cb)"""
+    def new_from_cbs(self, read_cb, write_cb, seek_cb, release_cb, hook=None):
+        assert self.data_cbs == None
+        self.data_cbs = pygpgme.new_PyObject_p_p()
         tmp = pygpgme.new_gpgme_data_t_p()
-        self._free_readcb()
-        self.last_readcb = pygpgme.new_PyObject_p_p()
-        hookdata = (funcs, hook)
-        pygpgme.pygpgme_data_new_from_cbs(tmp, hookdata, self.last_readcb)
+        if hook != None:
+            hookdata = (weakref.ref(self),
+                        read_cb, write_cb, seek_cb, release_cb, hook)
+        else:
+            hookdata = (weakref.ref(self),
+                        read_cb, write_cb, seek_cb, release_cb)
+        errorcheck(
+            pygpgme.pygpgme_data_new_from_cbs(tmp, hookdata, self.data_cbs))
         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
         pygpgme.delete_gpgme_data_t_p(tmp)
 
@@ -512,7 +519,10 @@ class Data(GpgmeWrapper):
         If a string is given, it is implicitly encoded using UTF-8."""
         written = pygpgme.gpgme_data_write(self.wrapped, buffer)
         if written < 0:
-            raise GPGMEError.fromSyserror()
+            if self._callback_excinfo:
+                pygpgme.pygpgme_raise_callback_exception(self)
+            else:
+                raise GPGMEError.fromSyserror()
         return written
 
     def read(self, size = -1):
@@ -527,11 +537,24 @@ class Data(GpgmeWrapper):
             return ''
 
         if size > 0:
-            return pygpgme.gpgme_data_read(self.wrapped, size)
+            try:
+                result = pygpgme.gpgme_data_read(self.wrapped, size)
+            except:
+                if self._callback_excinfo:
+                    pygpgme.pygpgme_raise_callback_exception(self)
+                else:
+                    raise
+            return result
         else:
             chunks = []
-            while 1:
-                result = pygpgme.gpgme_data_read(self.wrapped, 4096)
+            while True:
+                try:
+                    result = pygpgme.gpgme_data_read(self.wrapped, 4096)
+                except:
+                    if self._callback_excinfo:
+                        pygpgme.pygpgme_raise_callback_exception(self)
+                    else:
+                        raise
                 if len(result) == 0:
                     break
                 chunks.append(result)
diff --git a/lang/python/tests/t-callbacks.py b/lang/python/tests/t-callbacks.py
index 5797526..3219463 100755
--- a/lang/python/tests/t-callbacks.py
+++ b/lang/python/tests/t-callbacks.py
@@ -17,6 +17,7 @@
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
+import os
 from pyme import core, constants
 import support
 
@@ -181,3 +182,73 @@ except Exception as e:
     assert e == myException
 else:
     assert False, "Expected an error, got none"
+
+
+
+# Test the data callbacks.
+def read_cb(amount, hook=None):
+    assert hook == cookie
+    return 0
+def release_cb(hook=None):
+    assert hook == cookie
+data = core.Data(cbs=(read_cb, None, None, release_cb, cookie))
+try:
+    data.read()
+except Exception as e:
+    assert type(e) == TypeError
+else:
+    assert False, "Expected an error, got none"
+
+def read_cb(amount):
+    raise myException
+data = core.Data(cbs=(read_cb, None, None, lambda: None))
+try:
+    data.read()
+except Exception as e:
+    assert e == myException
+else:
+    assert False, "Expected an error, got none"
+
+
+def write_cb(what, hook=None):
+    assert hook == cookie
+    return "wrong type"
+data = core.Data(cbs=(None, write_cb, None, release_cb, cookie))
+try:
+    data.write(b'stuff')
+except Exception as e:
+    assert type(e) == TypeError
+else:
+    assert False, "Expected an error, got none"
+
+def write_cb(what):
+    raise myException
+data = core.Data(cbs=(None, write_cb, None, lambda: None))
+try:
+    data.write(b'stuff')
+except Exception as e:
+    assert e == myException
+else:
+    assert False, "Expected an error, got none"
+
+
+def seek_cb(offset, whence, hook=None):
+    assert hook == cookie
+    return "wrong type"
+data = core.Data(cbs=(None, None, seek_cb, release_cb, cookie))
+try:
+    data.seek(0, os.SEEK_SET)
+except Exception as e:
+    assert type(e) == TypeError
+else:
+    assert False, "Expected an error, got none"
+
+def seek_cb(offset, whence):
+    raise myException
+data = core.Data(cbs=(None, None, seek_cb, lambda: None))
+try:
+    data.seek(0, os.SEEK_SET)
+except Exception as e:
+    assert e == myException
+else:
+    assert False, "Expected an error, got none"
diff --git a/lang/python/tests/t-data.py b/lang/python/tests/t-data.py
index 6cf10fa..de60c47 100755
--- a/lang/python/tests/t-data.py
+++ b/lang/python/tests/t-data.py
@@ -17,6 +17,7 @@
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
+import io
 import os
 import tempfile
 from pyme import core
@@ -79,3 +80,43 @@ with tempfile.NamedTemporaryFile() as tmp:
     # Open using name, offset, and length.
     data = core.Data(file=tmp.name, offset=23, length=42)
     assert data.read() == binjunk[23:23+42]
+
+# Test callbacks.
+class DataObject(object):
+    def __init__(self):
+        self.buffer = io.BytesIO()
+        self.released = False
+
+    def read(self, amount, hook=None):
+        assert not self.released
+        return self.buffer.read(amount)
+
+    def write(self, data, hook=None):
+        assert not self.released
+        return self.buffer.write(data)
+
+    def seek(self, offset, whence, hook=None):
+        assert not self.released
+        return self.buffer.seek(offset, whence)
+
+    def release(self, hook=None):
+        assert not self.released
+        self.released = True
+
+do = DataObject()
+cookie = object()
+data = core.Data(cbs=(do.read, do.write, do.seek, do.release, cookie))
+data.write('Hello world!')
+data.seek(0, os.SEEK_SET)
+assert data.read() == b'Hello world!'
+del data
+assert do.released
+
+# Again, without the cookie.
+do = DataObject()
+data = core.Data(cbs=(do.read, do.write, do.seek, do.release))
+data.write('Hello world!')
+data.seek(0, os.SEEK_SET)
+assert data.read() == b'Hello world!'
+del data
+assert do.released

commit ebfe2300c33a3bad311e9ac1530e6c92636a08a4
Author: Justus Winter <justus at gnupg.org>
Date:   Fri May 27 12:25:59 2016 +0200

    python: Fix object deallocation.
    
    Handing a reference to the wrapper object created a non-trivial
    circular reference that Pythons garbage collector is unable to break.
    Explicitly break it by using a weak reference.
    
    * lang/python/helpers.c (pygpgme_stash_callback_exception): Retrieve
    object from weak reference.
    * lang/python/pyme/core.py (Context.__del__): Free status callback.
    (Context.set_passphrase_cb): Use a weak reference.
    (Context.set_progress_cb): Likewise.
    (Context.set_status_cb): Likewise.
    (Context.op_edit): Likewise.
    
    Signed-off-by: Justus Winter <justus at gnupg.org>

diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index ec7264a..9fe81c9 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -64,9 +64,9 @@ void pygpgme_clear_generic_cb(PyObject **cb) {
 /* Exception support for callbacks.  */
 #define EXCINFO	"_callback_excinfo"
 
-static void pygpgme_stash_callback_exception(PyObject *self)
+static void pygpgme_stash_callback_exception(PyObject *weak_self)
 {
-  PyObject *ptype, *pvalue, *ptraceback, *excinfo;
+  PyObject *self, *ptype, *pvalue, *ptraceback, *excinfo;
 
   PyErr_Fetch(&ptype, &pvalue, &ptraceback);
   excinfo = PyTuple_New(3);
@@ -86,7 +86,23 @@ static void pygpgme_stash_callback_exception(PyObject *self)
     PyTuple_SetItem(excinfo, 2, Py_None);
   }
 
-  PyObject_SetAttrString(self, EXCINFO, excinfo);
+  self = PyWeakref_GetObject(weak_self);
+  /* self only has a borrowed reference.  */
+  if (self == Py_None) {
+    /* This should not happen, as even if we're called from the data
+       release callback triggered from the wrappers destructor, the
+       object is still alive and hence the weak reference still refers
+       to the object.  However, in case this ever changes, not seeing
+       any exceptions is worse than having a little extra code, so
+       here we go.  */
+      fprintf(stderr,
+              "Error occurred in callback, but the wrapper object "
+              "has been deallocated.\n");
+      PyErr_Restore(ptype, pvalue, ptraceback);
+      PyErr_Print();
+    }
+  else
+    PyObject_SetAttrString(self, EXCINFO, excinfo);
 }
 
 PyObject *pygpgme_raise_callback_exception(PyObject *self)
diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index 0c2dd60..6ef2dab 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -16,9 +16,7 @@
 #    License along with this library; if not, write to the Free Software
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
-# import generators for portability with python2.2
-
-
+import weakref
 from . import pygpgme
 from .errors import errorcheck, GPGMEError
 from . import errors
@@ -149,6 +147,7 @@ class Context(GpgmeWrapper):
 
         self._free_passcb()
         self._free_progresscb()
+        self._free_statuscb()
         if self.own and pygpgme.gpgme_release:
             pygpgme.gpgme_release(self.wrapped)
 
@@ -252,9 +251,9 @@ class Context(GpgmeWrapper):
         else:
             self.last_passcb = pygpgme.new_PyObject_p_p()
             if hook == None:
-                hookdata = (self, func)
+                hookdata = (weakref.ref(self), func)
             else:
-                hookdata = (self, func, hook)
+                hookdata = (weakref.ref(self), func, hook)
         pygpgme.pygpgme_set_passphrase_cb(self.wrapped, hookdata, self.last_passcb)
 
     def set_progress_cb(self, func, hook=None):
@@ -275,9 +274,9 @@ class Context(GpgmeWrapper):
         else:
             self.last_progresscb = pygpgme.new_PyObject_p_p()
             if hook == None:
-                hookdata = (self, func)
+                hookdata = (weakref.ref(self), func)
             else:
-                hookdata = (self, func, hook)
+                hookdata = (weakref.ref(self), func, hook)
         pygpgme.pygpgme_set_progress_cb(self.wrapped, hookdata, self.last_progresscb)
 
     def set_status_cb(self, func, hook=None):
@@ -297,9 +296,9 @@ class Context(GpgmeWrapper):
         else:
             self.last_statuscb = pygpgme.new_PyObject_p_p()
             if hook == None:
-                hookdata = (self, func)
+                hookdata = (weakref.ref(self), func)
             else:
-                hookdata = (self, func, hook)
+                hookdata = (weakref.ref(self), func, hook)
         pygpgme.pygpgme_set_status_cb(self.wrapped, hookdata,
                                       self.last_statuscb)
 
@@ -333,9 +332,9 @@ class Context(GpgmeWrapper):
         if key == None:
             raise ValueError("op_edit: First argument cannot be None")
         if fnc_value:
-            opaquedata = (self, func, fnc_value)
+            opaquedata = (weakref.ref(self), func, fnc_value)
         else:
-            opaquedata = (self, func)
+            opaquedata = (weakref.ref(self), func)
 
         result = pygpgme.gpgme_op_edit(self.wrapped, key, opaquedata, out)
         if self._callback_excinfo:

commit e74cd9fb80f12b764d5e4561e73d55644147e9e7
Author: Justus Winter <justus at gnupg.org>
Date:   Fri May 27 12:19:52 2016 +0200

    Improve comments.
    
    * src/gpgme.h.in (gpgme_data_seek_cb_t, gpgme_data_seek): Clarify that
    these functions return the new offset.
    (gpgme_data_release_cb_t): Fix name of parameter.
    
    Signed-off-by: Justus Winter <justus at gnupg.org>

diff --git a/src/gpgme.h.in b/src/gpgme.h.in
index 335ed6b..87550fe 100644
--- a/src/gpgme.h.in
+++ b/src/gpgme.h.in
@@ -1196,11 +1196,12 @@ typedef @API__SSIZE_T@ (*gpgme_data_write_cb_t) (void *handle, const void *buffe
 
 /* Set the current position from where the next read or write starts
    in the data object with the handle HANDLE to OFFSET, relativ to
-   WHENCE.  */
+   WHENCE.  Returns the new offset in bytes from the beginning of the
+   data object.  */
 typedef @API__OFF_T@ (*gpgme_data_seek_cb_t) (void *handle,
                                        @API__OFF_T@ offset, int whence);
 
-/* Close the data object with the handle DL.  */
+/* Close the data object with the handle HANDLE.  */
 typedef void (*gpgme_data_release_cb_t) (void *handle);
 
 struct gpgme_data_cbs
@@ -1223,8 +1224,9 @@ typedef struct gpgme_data_cbs *gpgme_data_cbs_t;
 @API__SSIZE_T@ gpgme_data_write (gpgme_data_t dh, const void *buffer, size_t size);
 
 /* Set the current position from where the next read or write starts
-   in the data object with the handle DH to OFFSET, relativ to
-   WHENCE.  */
+   in the data object with the handle DH to OFFSET, relativ to WHENCE.
+   Returns the new offset in bytes from the beginning of the data
+   object.  */
 @API__OFF_T@ gpgme_data_seek (gpgme_data_t dh, @API__OFF_T@ offset, int whence);
 
 /* Create a new data buffer and return it in R_DH.  */

commit bf188e280b8b4fc775f33c47e2e1e275ed044004
Author: Justus Winter <justus at gnupg.org>
Date:   Wed May 25 12:47:28 2016 +0200

    python: Fix reading data from existing files.
    
    * lang/python/pyme/core.py (Data.__init__): Add 'copy' kwargument, and
    pass it to functions supporting it.  PEP8 fix.
    (Data.new_from_fd): PEP8 fix.
    (Data.new_from_file): Give a more helpful error message if copy is
    False.  PEP8 fix.
    (Data.new_from_fd): Hand the file descriptor to
    'gpgme_data_new_from_fd', not a stream.  Fix docstring.
    * lang/python/tests/t-data.py: Add tests for this.
    
    Signed-off-by: Justus Winter <justus at gnupg.org>

diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index 5f8378d..0c2dd60 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -370,8 +370,8 @@ class Data(GpgmeWrapper):
             return 0
         return 1
 
-    def __init__(self, string = None, file = None, offset = None,
-                 length = None, cbs = None):
+    def __init__(self, string=None, file=None, offset=None,
+                 length=None, cbs=None, copy=True):
         """Initialize a new gpgme_data_t object.
 
         If no args are specified, make it an empty object.
@@ -402,12 +402,12 @@ class Data(GpgmeWrapper):
         if cbs != None:
             self.new_from_cbs(*cbs)
         elif string != None:
-            self.new_from_mem(string)
+            self.new_from_mem(string, copy)
         elif file != None and offset != None and length != None:
             self.new_from_filepart(file, offset, length)
         elif file != None:
             if type(file) == type("x"):
-                self.new_from_file(file)
+                self.new_from_file(file, copy)
             else:
                 self.new_from_fd(file)
         else:
@@ -436,15 +436,21 @@ class Data(GpgmeWrapper):
         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
         pygpgme.delete_gpgme_data_t_p(tmp)
 
-    def new_from_mem(self, string, copy = 1):
+    def new_from_mem(self, string, copy=True):
         tmp = pygpgme.new_gpgme_data_t_p()
         errorcheck(pygpgme.gpgme_data_new_from_mem(tmp,string,len(string),copy))
         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
         pygpgme.delete_gpgme_data_t_p(tmp)
 
-    def new_from_file(self, filename, copy = 1):
+    def new_from_file(self, filename, copy=True):
         tmp = pygpgme.new_gpgme_data_t_p()
-        errorcheck(pygpgme.gpgme_data_new_from_file(tmp, filename, copy))
+        try:
+            errorcheck(pygpgme.gpgme_data_new_from_file(tmp, filename, copy))
+        except errors.GPGMEError as e:
+            if e.getcode() == errors.INV_VALUE and not copy:
+                raise ValueError("delayed reads are not yet supported")
+            else:
+                raise e
         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
         pygpgme.delete_gpgme_data_t_p(tmp)
 
@@ -485,16 +491,13 @@ class Data(GpgmeWrapper):
         pygpgme.delete_gpgme_data_t_p(tmp)
 
     def new_from_fd(self, file):
-        """This wraps the GPGME gpgme_data_new_from_fd() function.
-        The argument "file" may be a file-like object, supporting the fileno()
-        call and the mode attribute."""
+        """This wraps the GPGME gpgme_data_new_from_fd() function.  The
+        argument "file" must be a file-like object, supporting the
+        fileno() method.
 
+        """
         tmp = pygpgme.new_gpgme_data_t_p()
-        fp = pygpgme.fdopen(file.fileno(), file.mode)
-        if fp == None:
-            raise ValueError("Failed to open file from %s arg %s" % \
-                  (str(type(file)), str(file)))
-        errorcheck(pygpgme.gpgme_data_new_from_fd(tmp, fp))
+        errorcheck(pygpgme.gpgme_data_new_from_fd(tmp, file.fileno()))
         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
         pygpgme.delete_gpgme_data_t_p(tmp)
 
diff --git a/lang/python/tests/t-data.py b/lang/python/tests/t-data.py
index af2eb98..6cf10fa 100755
--- a/lang/python/tests/t-data.py
+++ b/lang/python/tests/t-data.py
@@ -18,7 +18,7 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 import os
-
+import tempfile
 from pyme import core
 
 data = core.Data('Hello world!')
@@ -32,6 +32,9 @@ assert data.read() == b''
 data = core.Data(b'Hello world!')
 assert data.read() == b'Hello world!'
 
+data = core.Data(b'Hello world!', copy=False)
+assert data.read() == b'Hello world!'
+
 data = core.Data()
 data.write('Hello world!')
 data.seek(0, os.SEEK_SET)
@@ -47,3 +50,32 @@ data = core.Data()
 data.write(binjunk)
 data.seek(0, os.SEEK_SET)
 assert data.read() == binjunk
+
+# Test reading from an existing file.
+with tempfile.NamedTemporaryFile() as tmp:
+    tmp.write(binjunk)
+    tmp.flush()
+    tmp.seek(0)
+
+    # Open using name.
+    data = core.Data(file=tmp.name)
+    assert data.read() == binjunk
+
+    # Open using name, without copying.
+    if False:
+        # delayed reads are not yet supported
+        data = core.Data(file=tmp.name, copy=False)
+        assert data.read() == binjunk
+
+    # Open using stream.
+    tmp.seek(0)
+    data = core.Data(file=tmp)
+    assert data.read() == binjunk
+
+    # Open using stream, offset, and length.
+    data = core.Data(file=tmp, offset=0, length=42)
+    assert data.read() == binjunk[:42]
+
+    # Open using name, offset, and length.
+    data = core.Data(file=tmp.name, offset=23, length=42)
+    assert data.read() == binjunk[23:23+42]

commit ce73ae9d0cbf782cd3a1949fc4f568f0d1da60d9
Author: Justus Winter <justus at gnupg.org>
Date:   Wed May 25 12:45:25 2016 +0200

    src: Fix trace string.
    
    * src/data-compat.c (gpgme_data_new_from_file): Fix trace string.
    
    Signed-off-by: Justus Winter <justus at gnupg.org>

diff --git a/src/data-compat.c b/src/data-compat.c
index abb7863..ec80172 100644
--- a/src/data-compat.c
+++ b/src/data-compat.c
@@ -128,7 +128,7 @@ gpgme_data_new_from_file (gpgme_data_t *r_dh, const char *fname, int copy)
 #else
   gpgme_error_t err;
   struct stat statbuf;
-  TRACE_BEG3 (DEBUG_DATA, "gpgme_data_new_from_filepart", r_dh,
+  TRACE_BEG3 (DEBUG_DATA, "gpgme_data_new_from_file", r_dh,
 	      "file_name=%s, copy=%i (%s)", fname, copy, copy ? "yes" : "no");
 
   if (!fname || !copy)

-----------------------------------------------------------------------

Summary of changes:
 lang/python/gpgme.i                                |   7 +-
 lang/python/helpers.c                              | 264 ++++++++++++++++++++-
 lang/python/helpers.h                              |   4 +
 lang/python/pyme/core.py                           | 128 ++++++----
 lang/python/tests/Makefile.am                      |   5 +-
 lang/python/tests/support.py                       |   3 +-
 lang/python/tests/t-callbacks.py                   |  71 ++++++
 lang/python/tests/t-data.py                        |  79 +++++-
 .../tests/{t-encrypt.py => t-encrypt-large.py}     |  32 ++-
 lang/python/tests/{t-encrypt.py => t-file-name.py} |  19 +-
 lang/python/tests/{t-decrypt.py => t-trustlist.py} |  16 +-
 src/data-compat.c                                  |   2 +-
 src/gpgme.h.in                                     |  12 +-
 tests/gpg/t-file-name.c                            |   2 +-
 14 files changed, 558 insertions(+), 86 deletions(-)
 copy lang/python/tests/{t-encrypt.py => t-encrypt-large.py} (63%)
 copy lang/python/tests/{t-encrypt.py => t-file-name.py} (74%)
 copy lang/python/tests/{t-decrypt.py => t-trustlist.py} (75%)


hooks/post-receive
-- 
GnuPG Made Easy
http://git.gnupg.org




More information about the Gnupg-commits mailing list