subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rhuij...@apache.org
Subject svn commit: r1846002 [44/44] - in /subversion/branches/ra-git: ./ build/ build/ac-macros/ build/generator/ build/generator/swig/ build/generator/templates/ build/generator/util/ build/win32/ contrib/client-side/ contrib/client-side/svn_load_dirs/ contr...
Date Wed, 07 Nov 2018 12:30:11 GMT
Modified: subversion/branches/ra-git/tools/dist/security/_gnupg.py
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/dist/security/_gnupg.py?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/dist/security/_gnupg.py (original)
+++ subversion/branches/ra-git/tools/dist/security/_gnupg.py Wed Nov  7 12:30:06 2018
@@ -1,3 +1,29 @@
+# Copyright (c) 2008-2014 by Vinay Sajip.
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+#    * Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#    * Redistributions in binary form must reproduce the above copyright notice,
+#      this list of conditions and the following disclaimer in the documentation
+#      and/or other materials provided with the distribution.
+#    * The name(s) of the copyright holder(s) may not be used to endorse or
+#      promote products derived from this software without specific prior
+#      written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) "AS IS" AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 """ A wrapper for the 'gpg' command::
 
 Portions of this module are derived from A.M. Kuchling's well-designed
@@ -27,18 +53,18 @@ Vinay Sajip to make use of the subproces
 and so does not work on Windows). Renamed to gnupg.py to avoid confusion with
 the previous versions.
 
-Modifications Copyright (C) 2008-2014 Vinay Sajip. All rights reserved.
+Modifications Copyright (C) 2008-2017 Vinay Sajip. All rights reserved.
 
 A unittest harness (test_gnupg.py) has also been added.
 """
 
-__version__ = "0.3.8.dev0"
+__version__ = "0.4.1"
 __author__ = "Vinay Sajip"
-__date__  = "$07-Dec-2014 18:46:17$"
+__date__  = "$07-Jul-2017 15:09:20$"
 
 try:
     from io import StringIO
-except ImportError:
+except ImportError:  # pragma: no cover
     from cStringIO import StringIO
 
 import codecs
@@ -53,7 +79,7 @@ import sys
 import threading
 
 STARTUPINFO = None
-if os.name == 'nt':
+if os.name == 'nt':  # pragma: no cover
     try:
         from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW, SW_HIDE
     except ImportError:
@@ -80,7 +106,7 @@ if not logger.handlers:
     logger.addHandler(NullHandler())
 
 # We use the test below because it works for Jython as well as CPython
-if os.path.__name__ == 'ntpath':
+if os.path.__name__ == 'ntpath':  # pragma: no cover
     # On Windows, we don't need shell quoting, other than worrying about
     # paths with spaces in them.
     def shell_quote(s):
@@ -105,7 +131,7 @@ else:
                  command shells
         :rtype: The passed-in type
         """
-        if not isinstance(s, string_types):
+        if not isinstance(s, string_types):  # pragma: no cover
             raise TypeError('Expected string type, got %s' % type(s))
         if not s:
             result = "''"
@@ -119,8 +145,18 @@ else:
 
 # Now that we use shell=False, we shouldn't need to quote arguments.
 # Use no_quote instead of shell_quote to remind us of where quoting
-# was needed.
+# was needed. However, note that we still need, on 2.x, to encode any
+# Unicode argument with the file system encoding - see Issue #41 and
+# Python issue #1759845 ("subprocess.call fails with unicode strings in
+# command line").
+
+# Allows the encoding used to be overridden in special cases by setting
+# this module attribute appropriately.
+fsencoding = sys.getfilesystemencoding()
+
 def no_quote(s):
+    if not _py3k and isinstance(s, text_type):
+        s = s.encode(fsencoding)
     return s
 
 def _copy_data(instream, outstream):
@@ -128,17 +164,23 @@ def _copy_data(instream, outstream):
     sent = 0
     if hasattr(sys.stdin, 'encoding'):
         enc = sys.stdin.encoding
-    else:
+    else:  # pragma: no cover
         enc = 'ascii'
     while True:
-        data = instream.read(1024)
+        # See issue #39: read can fail when e.g. a text stream is provided
+        # for what is actually a binary file
+        try:
+            data = instream.read(1024)
+        except UnicodeError:
+            logger.warning('Exception occurred while reading', exc_info=1)
+            break
         if not data:
             break
         sent += len(data)
-        logger.debug("sending chunk (%d): %r", sent, data[:256])
+        # logger.debug("sending chunk (%d): %r", sent, data[:256])
         try:
             outstream.write(data)
-        except UnicodeError:
+        except UnicodeError:  # pragma: no cover
             outstream.write(data.encode(enc))
         except:
             # Can sometimes get 'broken pipe' errors even when the data has all
@@ -147,7 +189,7 @@ def _copy_data(instream, outstream):
             break
     try:
         outstream.close()
-    except IOError:
+    except IOError:  # pragma: no cover
         logger.warning('Exception occurred while closing: ignored', exc_info=1)
     logger.debug("closed output, %d bytes sent", sent)
 
@@ -171,7 +213,7 @@ def _make_memory_stream(s):
     try:
         from io import BytesIO
         rv = BytesIO(s)
-    except ImportError:
+    except ImportError:  # pragma: no cover
         rv = StringIO(s)
     return rv
 
@@ -224,25 +266,20 @@ class Verify(object):
         if key in self.TRUST_LEVELS:
             self.trust_text = key
             self.trust_level = self.TRUST_LEVELS[key]
-        elif key in ("RSA_OR_IDEA", "NODATA", "IMPORT_RES", "PLAINTEXT",
-                     "PLAINTEXT_LENGTH", "POLICY_URL", "DECRYPTION_INFO",
-                     "DECRYPTION_OKAY", "INV_SGNR", "FILE_START", "FILE_ERROR",
-                     "FILE_DONE", "PKA_TRUST_GOOD", "PKA_TRUST_BAD", "BADMDC",
-                     "GOODMDC", "NO_SGNR", "NOTATION_NAME", "NOTATION_DATA",
-                     "PROGRESS", "PINENTRY_LAUNCHED", "NEWSIG"):
-            pass
-        elif key == "BADSIG":
+        elif key in ("WARNING", "ERROR"):
+            logger.warning('potential problem: %s: %s', key, value)
+        elif key == "BADSIG":  # pragma: no cover
             self.valid = False
             self.status = 'signature bad'
             self.key_id, self.username = value.split(None, 1)
-        elif key == "ERRSIG":
+        elif key == "ERRSIG":  # pragma: no cover
             self.valid = False
             (self.key_id,
              algo, hash_algo,
              cls,
              self.timestamp) = value.split()[:5]
             self.status = 'signature error'
-        elif key == "EXPSIG":
+        elif key == "EXPSIG":  # pragma: no cover
             self.valid = False
             self.status = 'signature expired'
             self.key_id, self.username = value.split(None, 1)
@@ -261,21 +298,15 @@ class Verify(object):
         elif key == "SIG_ID":
             (self.signature_id,
              self.creation_date, self.timestamp) = value.split()
-        elif key == "DECRYPTION_FAILED":
+        elif key == "DECRYPTION_FAILED":  # pragma: no cover
             self.valid = False
             self.key_id = value
             self.status = 'decryption failed'
-        elif key == "NO_PUBKEY":
+        elif key == "NO_PUBKEY":  # pragma: no cover
             self.valid = False
             self.key_id = value
             self.status = 'no public key'
-        elif key in ("KEYEXPIRED", "SIGEXPIRED", "KEYREVOKED"):
-            # these are useless in verify, since they are spit out for any
-            # pub/subkeys on the key, not just the one doing the signing.
-            # if we want to check for signatures with expired key,
-            # the relevant flag is EXPKEYSIG or REVKEYSIG.
-            pass
-        elif key in ("EXPKEYSIG", "REVKEYSIG"):
+        elif key in ("EXPKEYSIG", "REVKEYSIG"):  # pragma: no cover
             # signed with expired or revoked key
             self.valid = False
             self.key_id = value.split()[0]
@@ -284,12 +315,20 @@ class Verify(object):
             else:
                 self.key_status = 'signing key was revoked'
             self.status = self.key_status
-        elif key == "UNEXPECTED":
+        elif key in ("UNEXPECTED", "FAILURE"):  # pragma: no cover
             self.valid = False
             self.key_id = value
-            self.status = 'unexpected data'
-        else:
-            raise ValueError("Unknown status message: %r" % key)
+            if key == "UNEXPECTED":
+                self.status = 'unexpected data'
+            else:
+                # N.B. there might be other reasons
+                if not self.status:
+                    self.status = 'incorrect passphrase'
+        elif key in ("DECRYPTION_INFO", "PLAINTEXT", "PLAINTEXT_LENGTH",
+                     "NO_SECKEY", "BEGIN_SIGNING"):
+            pass
+        else:  # pragma: no cover
+            logger.debug('message ignored: %s, %s', key, value)
 
 class ImportResult(object):
     "Handle status messages for --import"
@@ -330,10 +369,12 @@ class ImportResult(object):
     }
 
     def handle_status(self, key, value):
-        if key == "IMPORTED":
+        if key in ("WARNING", "ERROR"):
+            logger.warning('potential problem: %s: %s', key, value)
+        elif key in ("IMPORTED", "KEY_CONSIDERED"):
             # this duplicates info we already see in import_ok & import_problem
             pass
-        elif key == "NODATA":
+        elif key == "NODATA":  # pragma: no cover
             self.results.append({'fingerprint': None,
                 'problem': '0', 'text': 'No valid data found'})
         elif key == "IMPORT_OK":
@@ -346,7 +387,7 @@ class ImportResult(object):
             self.results.append({'fingerprint': fingerprint,
                 'ok': reason, 'text': reasontext})
             self.fingerprints.append(fingerprint)
-        elif key == "IMPORT_PROBLEM":
+        elif key == "IMPORT_PROBLEM":  # pragma: no cover
             try:
                 reason, fingerprint = value.split()
             except:
@@ -358,19 +399,22 @@ class ImportResult(object):
             import_res = value.split()
             for i, count in enumerate(self.counts):
                 setattr(self, count, int(import_res[i]))
-        elif key == "KEYEXPIRED":
+        elif key == "KEYEXPIRED":  # pragma: no cover
             self.results.append({'fingerprint': None,
                 'problem': '0', 'text': 'Key expired'})
-        elif key == "SIGEXPIRED":
+        elif key == "SIGEXPIRED":  # pragma: no cover
             self.results.append({'fingerprint': None,
                 'problem': '0', 'text': 'Signature expired'})
-        else:
-            raise ValueError("Unknown status message: %r" % key)
+        elif key == "FAILURE":  # pragma: no cover
+            self.results.append({'fingerprint': None,
+                'problem': '0', 'text': 'Other failure'})
+        else:  # pragma: no cover
+            logger.debug('message ignored: %s, %s', key, value)
 
     def summary(self):
         l = []
         l.append('%d imported' % self.imported)
-        if self.not_imported:
+        if self.not_imported:  # pragma: no cover
             l.append('%d not imported' % self.not_imported)
         return ', '.join(l)
 
@@ -411,8 +455,12 @@ class SearchKeys(list):
     def get_fields(self, args):
         result = {}
         for i, var in enumerate(self.FIELDS):
-            result[var] = args[i]
+            if i < len(args):
+                result[var] = args[i]
+            else:
+                result[var] = 'unavailable'
         result['uids'] = []
+        result['sigs'] = []
         return result
 
     def pub(self, args):
@@ -427,11 +475,11 @@ class SearchKeys(list):
         self.curkey['uids'].append(uid)
         self.uids.append(uid)
 
-    def handle_status(self, key, value):
+    def handle_status(self, key, value):  # pragma: no cover
         pass
 
 class ListKeys(SearchKeys):
-    ''' Handle status messages for --list-keys.
+    ''' Handle status messages for --list-keys, --list-sigs.
 
         Handle pub and uid (relating the latter to the former).
 
@@ -439,7 +487,6 @@ class ListKeys(SearchKeys):
 
         crt = X.509 certificate
         crs = X.509 certificate and private key available
-        ssb = secret subkey (secondary key)
         uat = user attribute (same as user id except for field 10).
         sig = signature
         rev = revocation signature
@@ -449,7 +496,12 @@ class ListKeys(SearchKeys):
     '''
 
     UID_INDEX = 9
-    FIELDS = 'type trust length algo keyid date expires dummy ownertrust uid'.split()
+    FIELDS = 'type trust length algo keyid date expires dummy ownertrust uid sig'.split()
+
+    def __init__(self, gpg):
+        super(ListKeys, self).__init__(gpg)
+        self.in_subkey = False
+        self.key_map = {}
 
     def key(self, args):
         self.curkey = curkey = self.get_fields(args)
@@ -458,17 +510,35 @@ class ListKeys(SearchKeys):
         del curkey['uid']
         curkey['subkeys'] = []
         self.append(curkey)
+        self.in_subkey = False
 
     pub = sec = key
 
     def fpr(self, args):
-        self.curkey['fingerprint'] = args[9]
-        self.fingerprints.append(args[9])
+        fp = args[9]
+        if fp in self.key_map:  # pragma: no cover
+            raise ValueError('Unexpected fingerprint collision: %s' % fp)
+        if not self.in_subkey:
+            self.curkey['fingerprint'] = fp
+            self.fingerprints.append(fp)
+            self.key_map[fp] = self.curkey
+        else:
+            self.curkey['subkeys'][-1].append(fp)
+            self.key_map[fp] = self.curkey
 
     def sub(self, args):
-        subkey = [args[4], args[11]]
+        subkey = [args[4], args[11]]    # keyid, type
+        self.curkey['subkeys'].append(subkey)
+        self.in_subkey = True
+
+    def ssb(self, args):
+        subkey = [args[4], None]    # keyid, type
         self.curkey['subkeys'].append(subkey)
+        self.in_subkey = True
 
+    def sig(self, args):
+        # keyid, uid, sigclass
+        self.curkey['sigs'].append((args[4], args[9], args[10]))
 
 class ScanKeys(ListKeys):
     ''' Handle status messages for --with-fingerprint.'''
@@ -478,6 +548,7 @@ class ScanKeys(ListKeys):
         # use the last value args[-1] instead of args[11]
         subkey = [args[4], args[-1]]
         self.curkey['subkeys'].append(subkey)
+        self.in_subkey = True
 
 class TextHandler(object):
     def _as_text(self):
@@ -507,13 +578,10 @@ class Crypt(Verify, TextHandler):
     __bool__ = __nonzero__
 
     def handle_status(self, key, value):
-        if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
-                   "BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA", "PROGRESS",
-                   "CARDCTRL", "BADMDC", "SC_OP_FAILURE", "SC_OP_SUCCESS",
-                   "PINENTRY_LAUNCHED"):
-            # in the case of ERROR, this is because a more specific error
-            # message will have come first
-            pass
+        if key in ("WARNING", "ERROR"):
+            logger.warning('potential problem: %s: %s', key, value)
+        elif key == "NODATA":
+            self.status = "no data was provided"
         elif key in ("NEED_PASSPHRASE", "BAD_PASSPHRASE", "GOOD_PASSPHRASE",
                      "MISSING_PASSPHRASE", "DECRYPTION_FAILED",
                      "KEY_NOT_CREATED", "NEED_PASSPHRASE_PIN"):
@@ -530,14 +598,19 @@ class Crypt(Verify, TextHandler):
         elif key == "END_ENCRYPTION":
             self.status = 'encryption ok'
             self.ok = True
-        elif key == "INV_RECP":
+        elif key == "INV_RECP":  # pragma: no cover
             self.status = 'invalid recipient'
-        elif key == "KEYEXPIRED":
+        elif key == "KEYEXPIRED":  # pragma: no cover
             self.status = 'key expired'
-        elif key == "SIG_CREATED":
+        elif key == "SIG_CREATED":  # pragma: no cover
             self.status = 'sig created'
-        elif key == "SIGEXPIRED":
+        elif key == "SIGEXPIRED":  # pragma: no cover
             self.status = 'sig expired'
+        elif key in ("ENC_TO", "USERID_HINT", "GOODMDC",
+                     "END_DECRYPTION", "CARDCTRL", "BADMDC",
+                     "SC_OP_FAILURE", "SC_OP_SUCCESS",
+                     "PINENTRY_LAUNCHED", "KEY_CONSIDERED"):
+            pass
         else:
             Verify.handle_status(self, key, value)
 
@@ -558,13 +631,26 @@ class GenKey(object):
         return self.fingerprint or ''
 
     def handle_status(self, key, value):
-        if key in ("PROGRESS", "GOOD_PASSPHRASE", "NODATA", "KEY_NOT_CREATED",
-                   "PINENTRY_LAUNCHED"):
-            pass
+        if key in ("WARNING", "ERROR"):  # pragma: no cover
+            logger.warning('potential problem: %s: %s', key, value)
         elif key == "KEY_CREATED":
             (self.type,self.fingerprint) = value.split()
+        elif key in ("PROGRESS", "GOOD_PASSPHRASE", "KEY_NOT_CREATED"):
+            pass
+        else:  # pragma: no cover
+            logger.debug('message ignored: %s, %s', key, value)
+
+class ExportResult(GenKey):
+    """Handle status messages for --export[-secret-key].
+
+    For now, just use an existing class to base it on - if needed, we
+    can override handle_status for more specific message handling.
+    """
+    def handle_status(self, key, value):
+        if key in ("EXPORTED", "EXPORT_RES"):
+            pass
         else:
-            raise ValueError("Unknown status message: %r" % key)
+            super(ExportResult, self).handle_status(key, value)
 
 class DeleteResult(object):
     "Handle status messages for --delete-key and --delete-secret-key"
@@ -582,11 +668,11 @@ class DeleteResult(object):
     }
 
     def handle_status(self, key, value):
-        if key == "DELETE_PROBLEM":
+        if key == "DELETE_PROBLEM":  # pragma: no cover
             self.status = self.problem_reason.get(value,
                                                   "Unknown error: %r" % value)
-        else:
-            raise ValueError("Unknown status message: %r" % key)
+        else:  # pragma: no cover
+            logger.debug('message ignored: %s, %s', key, value)
 
     def __nonzero__(self):
         return self.status == 'ok'
@@ -601,6 +687,7 @@ class Sign(TextHandler):
         self.type = None
         self.hash_algo = None
         self.fingerprint = None
+        self.status = None
 
     def __nonzero__(self):
         return self.fingerprint is not None
@@ -608,22 +695,23 @@ class Sign(TextHandler):
     __bool__ = __nonzero__
 
     def handle_status(self, key, value):
-        if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
-                   "GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL", "INV_SGNR",
-                   "NO_SGNR", "MISSING_PASSPHRASE", "NEED_PASSPHRASE_PIN",
-                   "SC_OP_FAILURE", "SC_OP_SUCCESS", "PROGRESS"):
-            pass
-        elif key in ("KEYEXPIRED", "SIGEXPIRED"):
+        if key in ("WARNING", "ERROR", "FAILURE"):  # pragma: no cover
+            logger.warning('potential problem: %s: %s', key, value)
+        elif key in ("KEYEXPIRED", "SIGEXPIRED"):  # pragma: no cover
             self.status = 'key expired'
-        elif key == "KEYREVOKED":
+        elif key == "KEYREVOKED":  # pragma: no cover
             self.status = 'key revoked'
         elif key == "SIG_CREATED":
             (self.type,
              algo, self.hash_algo, cls,
              self.timestamp, self.fingerprint
              ) = value.split()
-        else:
-            raise ValueError("Unknown status message: %r" % key)
+            self.status = 'signature created'
+        elif key in ("USERID_HINT", "NEED_PASSPHRASE", "GOOD_PASSPHRASE",
+                     "BAD_PASSPHRASE", "BEGIN_SIGNING"):
+            pass
+        else:  # pragma: no cover
+            logger.debug('message ignored: %s, %s', key, value)
 
 VERSION_RE = re.compile(r'gpg \(GnuPG\) (\d+(\.\d+)*)'.encode('ascii'), re.I)
 HEX_DIGITS_RE = re.compile(r'[0-9a-f]+$', re.I)
@@ -643,6 +731,7 @@ class GPG(object):
         'search': SearchKeys,
         'sign': Sign,
         'verify': Verify,
+        'export': ExportResult,
     }
 
     "Encapsulate access to the gpg executable"
@@ -677,9 +766,10 @@ class GPG(object):
         self.secret_keyring = secret_keyring
         self.verbose = verbose
         self.use_agent = use_agent
-        if isinstance(options, str):
+        if isinstance(options, str):  # pragma: no cover
             options = [options]
         self.options = options
+        self.on_data = None  # or a callable - will be called with data chunks
         # Changed in 0.3.7 to use Latin-1 encoding rather than
         # locale.getpreferredencoding falling back to sys.stdin.encoding
         # falling back to utf-8, because gpg itself uses latin-1 as the default
@@ -687,14 +777,19 @@ class GPG(object):
         self.encoding = 'latin-1'
         if gnupghome and not os.path.isdir(self.gnupghome):
             os.makedirs(self.gnupghome,0x1C0)
-        p = self._open_subprocess(["--version"])
+        try:
+            p = self._open_subprocess(["--version"])
+        except OSError:
+            msg = 'Unable to run gpg - it may not be available.'
+            logger.exception(msg)
+            raise OSError(msg)
         result = self.result_map['verify'](self) # any result will do for this
         self._collect_output(p, result, stdin=p.stdin)
-        if p.returncode != 0:
+        if p.returncode != 0:  # pragma: no cover
             raise ValueError("Error invoking gpg: %s: %s" % (p.returncode,
                                                              result.stderr))
         m = VERSION_RE.match(result.data)
-        if not m:
+        if not m:  # pragma: no cover
             self.version = None
         else:
             dot = '.'.encode('ascii')
@@ -707,6 +802,11 @@ class GPG(object):
         a passphrase will be sent to GPG, else False.
         """
         cmd = [self.gpgbinary, '--status-fd', '2', '--no-tty']
+        cmd.extend(['--debug', 'ipc'])
+        if passphrase and hasattr(self, 'version'):
+            if self.version >= (2, 1):
+                cmd[1:1] = ['--pinentry-mode', 'loopback']
+        cmd.extend(['--fixed-list-mode', '--batch', '--with-colons'])
         if self.gnupghome:
             cmd.extend(['--homedir',  no_quote(self.gnupghome)])
         if self.keyring:
@@ -717,8 +817,8 @@ class GPG(object):
             for fn in self.secret_keyring:
                 cmd.extend(['--secret-keyring', no_quote(fn)])
         if passphrase:
-            cmd.extend(['--batch', '--passphrase-fd', '0'])
-        if self.use_agent:
+            cmd.extend(['--passphrase-fd', '0'])
+        if self.use_agent:  # pragma: no cover
             cmd.append('--use-agent')
         if self.options:
             cmd.extend(self.options)
@@ -728,19 +828,35 @@ class GPG(object):
     def _open_subprocess(self, args, passphrase=False):
         # Internal method: open a pipe to a GPG subprocess and return
         # the file objects for communicating with it.
+
+        # def debug_print(cmd):
+            # result = []
+            # for c in cmd:
+                # if ' ' not in c:
+                    # result.append(c)
+                # else:
+                    # if '"' not in c:
+                        # result.append('"%s"' % c)
+                    # elif "'" not in c:
+                        # result.append("'%s'" % c)
+                    # else:
+                        # result.append(c)  # give up
+            # return ' '.join(cmd)
+        from subprocess import list2cmdline as debug_print
+
         cmd = self.make_args(args, passphrase)
-        if self.verbose:
-            pcmd = ' '.join(cmd)
-            print(pcmd)
-        logger.debug("%s", cmd)
+        if self.verbose:  # pragma: no cover
+            print(debug_print(cmd))
         if not STARTUPINFO:
             si = None
-        else:
+        else:  # pragma: no cover
             si = STARTUPINFO()
             si.dwFlags = STARTF_USESHOWWINDOW
             si.wShowWindow = SW_HIDE
-        return Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE,
-                     startupinfo=si)
+        result = Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE,
+                       startupinfo=si)
+        logger.debug("%s: %s", result.pid, debug_print(cmd))
+        return result
 
     def _read_response(self, stream, result):
         # Internal method: reads all the stderr output from GPG, taking notice
@@ -755,7 +871,7 @@ class GPG(object):
                 break
             lines.append(line)
             line = line.rstrip()
-            if self.verbose:
+            if self.verbose:  # pragma: no cover
                 print(line)
             logger.debug("%s", line)
             if line[0:9] == '[GNUPG:] ':
@@ -770,7 +886,7 @@ class GPG(object):
                 result.handle_status(keyword, value)
         result.stderr = ''.join(lines)
 
-    def _read_data(self, stream, result):
+    def _read_data(self, stream, result, on_data=None):
         # Read the contents of the file from GPG's stdout
         chunks = []
         while True:
@@ -779,6 +895,8 @@ class GPG(object):
                 break
             logger.debug("chunk: %r" % data[:256])
             chunks.append(data)
+            if on_data:
+                on_data(data)
         if _py3k:
             # Join using b'' or '', as appropriate
             result.data = type(data)().join(chunks)
@@ -799,7 +917,7 @@ class GPG(object):
         rr.start()
 
         stdout = process.stdout
-        dr = threading.Thread(target=self._read_data, args=(stdout, result))
+        dr = threading.Thread(target=self._read_data, args=(stdout, result, self.on_data))
         dr.setDaemon(True)
         logger.debug('stdout reader: %r', dr)
         dr.start()
@@ -812,7 +930,7 @@ class GPG(object):
         if stdin is not None:
             try:
                 stdin.close()
-            except IOError:
+            except IOError:  # pragma: no cover
                 pass
         stderr.close()
         stdout.close()
@@ -822,7 +940,7 @@ class GPG(object):
         # Handle a basic data call - pass data to GPG, handle the output
         # including status information. Garbage In, Garbage Out :)
         p = self._open_subprocess(args, passphrase is not None)
-        if not binary:
+        if not binary:  # pragma: no cover
             stdin = codecs.getwriter(self.encoding)(p.stdin)
         else:
             stdin = p.stdin
@@ -846,14 +964,14 @@ class GPG(object):
         "If writing to a file which exists, avoid a confirmation message."
         if os.path.exists(output):
             # We need to avoid an overwrite confirmation message
-            args.extend(['--batch', '--yes'])
-        args.extend(['--output', output])
+            args.extend(['--yes'])
+        args.extend(['--output', no_quote(output)])
 
     def sign_file(self, file, keyid=None, passphrase=None, clearsign=True,
-                  detach=False, binary=False, output=None):
+                  detach=False, binary=False, output=None, extra_args=None):
         """sign file"""
         logger.debug("sign_file: %s", file)
-        if binary:
+        if binary:  # pragma: no cover
             args = ['-s']
         else:
             args = ['-sa']
@@ -868,6 +986,8 @@ class GPG(object):
         if output:  # write the output to a file with the specified name
             self.set_output_without_confirmation(args, output)
 
+        if extra_args:
+            args.extend(extra_args)
         result = self.result_map['sign'](self)
         #We could use _handle_io here except for the fact that if the
         #passphrase is bad, gpg bails and you can't write the message.
@@ -877,17 +997,18 @@ class GPG(object):
             if passphrase:
                 _write_passphrase(stdin, passphrase, self.encoding)
             writer = _threaded_copy_data(file, stdin)
-        except IOError:
+        except IOError:  # pragma: no cover
             logging.exception("error writing message")
             writer = None
         self._collect_output(p, result, writer, stdin)
         return result
 
-    def verify(self, data):
+    def verify(self, data, **kwargs):
         """Verify the signature on the contents of the string 'data'
 
-        >>> gpg = GPG(gnupghome="keys")
-        >>> input = gpg.gen_key_input(Passphrase='foo')
+        >>> GPGBINARY = os.environ.get('GPGBINARY', 'gpg')
+        >>> gpg = GPG(gpgbinary=GPGBINARY, gnupghome="keys")
+        >>> input = gpg.gen_key_input(passphrase='foo')
         >>> key = gpg.gen_key(input)
         >>> assert key
         >>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='bar')
@@ -899,15 +1020,17 @@ class GPG(object):
 
         """
         f = _make_binary_stream(data, self.encoding)
-        result = self.verify_file(f)
+        result = self.verify_file(f, **kwargs)
         f.close()
         return result
 
-    def verify_file(self, file, data_filename=None):
+    def verify_file(self, file, data_filename=None, close_file=True, extra_args=None):
         "Verify the signature on the contents of the file-like object 'file'"
         logger.debug('verify_file: %r, %r', file, data_filename)
         result = self.result_map['verify'](self)
         args = ['--verify']
+        if extra_args:
+            args.extend(extra_args)
         if data_filename is None:
             self._handle_io(args, file, result, binary=True)
         else:
@@ -915,7 +1038,8 @@ class GPG(object):
             import tempfile
             fd, fn = tempfile.mkstemp(prefix='pygpg')
             s = file.read()
-            file.close()
+            if close_file:
+                file.close()
             logger.debug('Wrote to temp file: %r', s)
             os.write(fd, s)
             os.close(fd)
@@ -928,11 +1052,14 @@ class GPG(object):
                 os.unlink(fn)
         return result
 
-    def verify_data(self, sig_filename, data):
+    def verify_data(self, sig_filename, data, extra_args=None):
         "Verify the signature in sig_filename against data in memory"
         logger.debug('verify_data: %r, %r ...', sig_filename, data[:16])
         result = self.result_map['verify'](self)
-        args = ['--verify', no_quote(sig_filename), '-']
+        args = ['--verify']
+        if extra_args:
+            args.extend(extra_args)
+        args.extend([no_quote(sig_filename), '-'])
         stream = _make_memory_stream(data)
         self._handle_io(args, stream, result, binary=True)
         return result
@@ -942,49 +1069,8 @@ class GPG(object):
     #
 
     def import_keys(self, key_data):
-        """ import the key_data into our keyring
-
-        >>> import shutil
-        >>> shutil.rmtree("keys")
-        >>> gpg = GPG(gnupghome="keys")
-        >>> input = gpg.gen_key_input()
-        >>> result = gpg.gen_key(input)
-        >>> print1 = result.fingerprint
-        >>> result = gpg.gen_key(input)
-        >>> print2 = result.fingerprint
-        >>> pubkey1 = gpg.export_keys(print1)
-        >>> seckey1 = gpg.export_keys(print1,secret=True)
-        >>> seckeys = gpg.list_keys(secret=True)
-        >>> pubkeys = gpg.list_keys()
-        >>> assert print1 in seckeys.fingerprints
-        >>> assert print1 in pubkeys.fingerprints
-        >>> str(gpg.delete_keys(print1))
-        'Must delete secret key first'
-        >>> str(gpg.delete_keys(print1,secret=True))
-        'ok'
-        >>> str(gpg.delete_keys(print1))
-        'ok'
-        >>> str(gpg.delete_keys("nosuchkey"))
-        'No such key'
-        >>> seckeys = gpg.list_keys(secret=True)
-        >>> pubkeys = gpg.list_keys()
-        >>> assert not print1 in seckeys.fingerprints
-        >>> assert not print1 in pubkeys.fingerprints
-        >>> result = gpg.import_keys('foo')
-        >>> assert not result
-        >>> result = gpg.import_keys(pubkey1)
-        >>> pubkeys = gpg.list_keys()
-        >>> seckeys = gpg.list_keys(secret=True)
-        >>> assert not print1 in seckeys.fingerprints
-        >>> assert print1 in pubkeys.fingerprints
-        >>> result = gpg.import_keys(seckey1)
-        >>> assert result
-        >>> seckeys = gpg.list_keys(secret=True)
-        >>> pubkeys = gpg.list_keys()
-        >>> assert print1 in seckeys.fingerprints
-        >>> assert print1 in pubkeys.fingerprints
-        >>> assert print2 in pubkeys.fingerprints
-
+        """
+        Import the key_data into our keyring.
         """
         result = self.result_map['import'](self)
         logger.debug('import_keys: %r', key_data[:256])
@@ -998,10 +1084,11 @@ class GPG(object):
         """Import a key from a keyserver
 
         >>> import shutil
-        >>> shutil.rmtree("keys")
-        >>> gpg = GPG(gnupghome="keys")
+        >>> shutil.rmtree("keys", ignore_errors=True)
+        >>> GPGBINARY = os.environ.get('GPGBINARY', 'gpg')
+        >>> gpg = GPG(gpgbinary=GPGBINARY, gnupghome="keys")
         >>> os.chmod('keys', 0x1C0)
-        >>> result = gpg.recv_keys('keyserver.ubuntu.com', '92905378')
+        >>> result = gpg.recv_keys('pgp.mit.edu', '92905378')
         >>> assert result
 
         """
@@ -1033,26 +1120,48 @@ class GPG(object):
         data.close()
         return result
 
-    def delete_keys(self, fingerprints, secret=False):
+    def delete_keys(self, fingerprints, secret=False, passphrase=None):
         which='key'
-        if secret:
+        if secret:  # pragma: no cover
+            if self.version >= (2, 1) and passphrase is None:
+                raise ValueError('For GnuPG >= 2.1, deleting secret keys '
+                                 'needs a passphrase to be provided')
             which='secret-key'
-        if _is_sequence(fingerprints):
+        if _is_sequence(fingerprints):  # pragma: no cover
             fingerprints = [no_quote(s) for s in fingerprints]
         else:
             fingerprints = [no_quote(fingerprints)]
-        args = ['--batch', '--delete-%s' % which]
+        args = ['--delete-%s' % which]
         args.extend(fingerprints)
         result = self.result_map['delete'](self)
-        p = self._open_subprocess(args)
-        self._collect_output(p, result, stdin=p.stdin)
+        if not secret or self.version < (2, 1):
+            p = self._open_subprocess(args)
+            self._collect_output(p, result, stdin=p.stdin)
+        else:
+            # Need to send in a passphrase.
+            f = _make_binary_stream('', self.encoding)
+            try:
+                self._handle_io(args, f, result, passphrase=passphrase,
+                                binary=True)
+            finally:
+                f.close()
         return result
 
-    def export_keys(self, keyids, secret=False, armor=True, minimal=False):
-        "export the indicated keys. 'keyid' is anything gpg accepts"
+    def export_keys(self, keyids, secret=False, armor=True, minimal=False,
+                    passphrase=None):
+        """
+        Export the indicated keys. A 'keyid' is anything gpg accepts.
+
+        Since GnuPG 2.1, you can't export secret keys without providing a
+        passphrase.
+        """
+
         which=''
         if secret:
             which='-secret-key'
+            if self.version >= (2, 1) and passphrase is None:
+                raise ValueError('For GnuPG >= 2.1, exporting secret keys '
+                                 'needs a passphrase to be provided')
         if _is_sequence(keyids):
             keyids = [no_quote(k) for k in keyids]
         else:
@@ -1060,17 +1169,30 @@ class GPG(object):
         args = ['--export%s' % which]
         if armor:
             args.append('--armor')
-        if minimal:
+        if minimal:  # pragma: no cover
             args.extend(['--export-options','export-minimal'])
         args.extend(keyids)
-        p = self._open_subprocess(args)
         # gpg --export produces no status-fd output; stdout will be
         # empty in case of failure
         #stdout, stderr = p.communicate()
-        result = self.result_map['delete'](self) # any result will do
-        self._collect_output(p, result, stdin=p.stdin)
+        result = self.result_map['export'](self)
+        if not secret or self.version < (2, 1):
+            p = self._open_subprocess(args)
+            self._collect_output(p, result, stdin=p.stdin)
+        else:
+            # Need to send in a passphrase.
+            f = _make_binary_stream('', self.encoding)
+            try:
+                self._handle_io(args, f, result, passphrase=passphrase,
+                                binary=True)
+            finally:
+                f.close()
         logger.debug('export_keys result: %r', result.data)
-        return result.data.decode(self.encoding, self.decode_errors)
+        # Issue #49: Return bytes if armor not specified, else text
+        result = result.data
+        if armor:
+            result = result.decode(self.encoding, self.decode_errors)
+        return result
 
     def _get_list_output(self, p, kind):
         # Get the response information
@@ -1078,43 +1200,50 @@ class GPG(object):
         self._collect_output(p, result, stdin=p.stdin)
         lines = result.data.decode(self.encoding,
                                    self.decode_errors).splitlines()
-        valid_keywords = 'pub uid sec fpr sub'.split()
+        valid_keywords = 'pub uid sec fpr sub ssb sig'.split()
         for line in lines:
-            if self.verbose:
+            if self.verbose:  # pragma: no cover
                 print(line)
             logger.debug("line: %r", line.rstrip())
-            if not line:
+            if not line:  # pragma: no cover
                 break
             L = line.strip().split(':')
-            if not L:
+            if not L:  # pragma: no cover
                 continue
             keyword = L[0]
             if keyword in valid_keywords:
                 getattr(result, keyword)(L)
         return result
 
-    def list_keys(self, secret=False):
+    def list_keys(self, secret=False, keys=None, sigs=False):
         """ list the keys currently in the keyring
 
         >>> import shutil
-        >>> shutil.rmtree("keys")
-        >>> gpg = GPG(gnupghome="keys")
-        >>> input = gpg.gen_key_input()
+        >>> shutil.rmtree("keys", ignore_errors=True)
+        >>> GPGBINARY = os.environ.get('GPGBINARY', 'gpg')
+        >>> gpg = GPG(gpgbinary=GPGBINARY, gnupghome="keys")
+        >>> input = gpg.gen_key_input(passphrase='foo')
         >>> result = gpg.gen_key(input)
-        >>> print1 = result.fingerprint
+        >>> fp1 = result.fingerprint
         >>> result = gpg.gen_key(input)
-        >>> print2 = result.fingerprint
+        >>> fp2 = result.fingerprint
         >>> pubkeys = gpg.list_keys()
-        >>> assert print1 in pubkeys.fingerprints
-        >>> assert print2 in pubkeys.fingerprints
+        >>> assert fp1 in pubkeys.fingerprints
+        >>> assert fp2 in pubkeys.fingerprints
 
         """
 
-        which='keys'
+        if sigs:
+            which = 'sigs'
+        else:            which='keys'
         if secret:
             which='secret-keys'
-        args = ["--list-%s" % which, "--fixed-list-mode", "--fingerprint",
-                "--with-colons"]
+        args = ['--list-%s' % which,
+                '--fingerprint', '--fingerprint'] # get subkey FPs, too
+        if keys:
+            if isinstance(keys, string_types):
+                keys = [keys]
+            args.extend(keys)
         p = self._open_subprocess(args)
         return self._get_list_output(p, 'list')
 
@@ -1123,10 +1252,20 @@ class GPG(object):
         List details of an ascii armored or binary key file
         without first importing it to the local keyring.
 
-        The function achieves this by running:
+        The function achieves this on modern GnuPG by running:
+
+        $ gpg --dry-run --import-options import-show --import
+
+        On older versions, it does the *much* riskier:
+
         $ gpg --with-fingerprint --with-colons filename
         """
-        args = ['--with-fingerprint', '--with-colons']
+        if self.version >= (2, 1):
+            args = ['--dry-run', '--import-options', 'import-show', '--import']
+        else:
+            logger.warning('Trying to list packets, but if the file is not a '
+                           'keyring, might accidentally decrypt')
+            args = ['--with-fingerprint', '--with-colons', '--fixed-list-mode']
         args.append(no_quote(filename))
         p = self._open_subprocess(args)
         return self._get_list_output(p, 'scan')
@@ -1135,20 +1274,21 @@ class GPG(object):
         """ search keyserver by query (using --search-keys option)
 
         >>> import shutil
-        >>> shutil.rmtree('keys')
-        >>> gpg = GPG(gnupghome='keys')
+        >>> shutil.rmtree('keys', ignore_errors=True)
+        >>> GPGBINARY = os.environ.get('GPGBINARY', 'gpg')
+        >>> gpg = GPG(gpgbinary=GPGBINARY, gnupghome='keys')
         >>> os.chmod('keys', 0x1C0)
         >>> result = gpg.search_keys('<vinay_sajip@hotmail.com>')
         >>> assert result, 'Failed using default keyserver'
-        >>> keyserver = 'keyserver.ubuntu.com'
-        >>> result = gpg.search_keys('<vinay_sajip@hotmail.com>', keyserver)
-        >>> assert result, 'Failed using keyserver.ubuntu.com'
+        >>> #keyserver = 'keyserver.ubuntu.com'
+        >>> #result = gpg.search_keys('<vinay_sajip@hotmail.com>', keyserver)
+        >>> #assert result, 'Failed using keyserver.ubuntu.com'
 
         """
         query = query.strip()
         if HEX_DIGITS_RE.match(query):
             query = '0x' + query
-        args = ['--fixed-list-mode', '--fingerprint', '--with-colons',
+        args = ['--fingerprint',
                 '--keyserver', no_quote(keyserver), '--search-keys',
                 no_quote(query)]
         p = self._open_subprocess(args)
@@ -1160,13 +1300,13 @@ class GPG(object):
                                    self.decode_errors).splitlines()
         valid_keywords = ['pub', 'uid']
         for line in lines:
-            if self.verbose:
+            if self.verbose:  # pragma: no cover
                 print(line)
             logger.debug('line: %r', line.rstrip())
             if not line:    # sometimes get blank lines on Windows
                 continue
             L = line.strip().split(':')
-            if not L:
+            if not L:  # pragma: no cover
                 continue
             keyword = L[0]
             if keyword in valid_keywords:
@@ -1177,15 +1317,16 @@ class GPG(object):
         """Generate a key; you might use gen_key_input() to create the
         control input.
 
-        >>> gpg = GPG(gnupghome="keys")
-        >>> input = gpg.gen_key_input()
+        >>> GPGBINARY = os.environ.get('GPGBINARY', 'gpg')
+        >>> gpg = GPG(gpgbinary=GPGBINARY, gnupghome="keys")
+        >>> input = gpg.gen_key_input(passphrase='foo')
         >>> result = gpg.gen_key(input)
         >>> assert result
         >>> result = gpg.gen_key('foo')
         >>> assert not result
 
         """
-        args = ["--gen-key", "--batch"]
+        args = ["--gen-key"]
         result = self.result_map['generate'](self)
         f = _make_binary_stream(input, self.encoding)
         self._handle_io(args, f, result, binary=True)
@@ -1242,7 +1383,7 @@ class GPG(object):
     #
     def encrypt_file(self, file, recipients, sign=None,
             always_trust=False, passphrase=None,
-            armor=True, output=None, symmetric=False):
+            armor=True, output=None, symmetric=False, extra_args=None):
         "Encrypt the message read from the file-like object 'file'"
         args = ['--encrypt']
         if symmetric:
@@ -1264,12 +1405,14 @@ class GPG(object):
             args.append('--armor')
         if output:  # write the output to a file with the specified name
             self.set_output_without_confirmation(args, output)
-        if sign is True:
+        if sign is True:  # pragma: no cover
             args.append('--sign')
-        elif sign:
+        elif sign:  # pragma: no cover
             args.extend(['--sign', '--default-key', no_quote(sign)])
-        if always_trust:
+        if always_trust:  # pragma: no cover
             args.append('--always-trust')
+        if extra_args:
+            args.extend(extra_args)
         result = self.result_map['crypt'](self)
         self._handle_io(args, file, result, passphrase=passphrase, binary=True)
         logger.debug('encrypt result: %r', result.data)
@@ -1280,40 +1423,41 @@ class GPG(object):
 
         >>> import shutil
         >>> if os.path.exists("keys"):
-        ...     shutil.rmtree("keys")
-        >>> gpg = GPG(gnupghome="keys")
-        >>> input = gpg.gen_key_input(passphrase='foo')
+        ...     shutil.rmtree("keys", ignore_errors=True)
+        >>> GPGBINARY = os.environ.get('GPGBINARY', 'gpg')
+        >>> gpg = GPG(gpgbinary=GPGBINARY, gnupghome="keys")
+        >>> input = gpg.gen_key_input(name_email='user1@test', passphrase='pp1')
         >>> result = gpg.gen_key(input)
-        >>> print1 = result.fingerprint
-        >>> input = gpg.gen_key_input()
+        >>> fp1 = result.fingerprint
+        >>> input = gpg.gen_key_input(name_email='user2@test', passphrase='pp2')
         >>> result = gpg.gen_key(input)
-        >>> print2 = result.fingerprint
-        >>> result = gpg.encrypt("hello",print2)
+        >>> fp2 = result.fingerprint
+        >>> result = gpg.encrypt("hello",fp2)
         >>> message = str(result)
         >>> assert message != 'hello'
-        >>> result = gpg.decrypt(message)
+        >>> result = gpg.decrypt(message, passphrase='pp2')
         >>> assert result
         >>> str(result)
         'hello'
-        >>> result = gpg.encrypt("hello again",print1)
+        >>> result = gpg.encrypt("hello again", fp1)
         >>> message = str(result)
-        >>> result = gpg.decrypt(message,passphrase='bar')
+        >>> result = gpg.decrypt(message, passphrase='bar')
         >>> result.status in ('decryption failed', 'bad passphrase')
         True
         >>> assert not result
-        >>> result = gpg.decrypt(message,passphrase='foo')
+        >>> result = gpg.decrypt(message, passphrase='pp1')
         >>> result.status == 'decryption ok'
         True
         >>> str(result)
         'hello again'
-        >>> result = gpg.encrypt("signed hello",print2,sign=print1,passphrase='foo')
+        >>> result = gpg.encrypt("signed hello", fp2, sign=fp1, passphrase='pp1')
         >>> result.status == 'encryption ok'
         True
         >>> message = str(result)
-        >>> result = gpg.decrypt(message)
+        >>> result = gpg.decrypt(message, passphrase='pp2')
         >>> result.status == 'decryption ok'
         True
-        >>> assert result.fingerprint == print1
+        >>> assert result.fingerprint == fp1
 
         """
         data = _make_binary_stream(data, self.encoding)
@@ -1328,14 +1472,15 @@ class GPG(object):
         return result
 
     def decrypt_file(self, file, always_trust=False, passphrase=None,
-                     output=None):
+                     output=None, extra_args=None):
         args = ["--decrypt"]
         if output:  # write the output to a file with the specified name
             self.set_output_without_confirmation(args, output)
-        if always_trust:
+        if always_trust:  # pragma: no cover
             args.append("--always-trust")
+        if extra_args:
+            args.extend(extra_args)
         result = self.result_map['crypt'](self)
         self._handle_io(args, file, result, passphrase, binary=True)
         logger.debug('decrypt result: %r', result.data)
         return result
-

Modified: subversion/branches/ra-git/tools/dist/security/mailer.py
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/dist/security/mailer.py?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/dist/security/mailer.py (original)
+++ subversion/branches/ra-git/tools/dist/security/mailer.py Wed Nov  7 12:30:06 2018
@@ -232,6 +232,9 @@ class Mailer(object):
             server.login(username, password)
 
         def send(message):
+            # XXX: The from,to arguments should be bare addresses with no "foo:"
+            #      prefix.  It works this way in practice, but that appears to
+            #      be an accident of implementation of smtplib.
             server.sendmail("From: " + message['From'],
                             "To: " + message['To'],
                             message.as_string())

Modified: subversion/branches/ra-git/tools/dist/security/parser.py
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/dist/security/parser.py?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/dist/security/parser.py (original)
+++ subversion/branches/ra-git/tools/dist/security/parser.py Wed Nov  7 12:30:06 2018
@@ -50,9 +50,16 @@ class Notification(object):
         CULPRIT_SERVER = 'server'
         CULPRIT_CLIENT = 'client'
 
-        __CULPRITS = ((CULPRIT_SERVER, CULPRIT_CLIENT,
-                      (CULPRIT_SERVER, CULPRIT_CLIENT),
-                      (CULPRIT_CLIENT, CULPRIT_SERVER)))
+        # For compatibility, 'client' and 'server' may be specified either with
+        # or without a tuple.
+        __CULPRITS = (
+            CULPRIT_SERVER,
+            CULPRIT_CLIENT,
+            (CULPRIT_SERVER,)
+            (CULPRIT_CLIENT,)
+            (CULPRIT_SERVER, CULPRIT_CLIENT),
+            (CULPRIT_CLIENT, CULPRIT_SERVER),
+        )
 
         def __init__(self, basedir, tracking_id,
                      title, culprit, advisory, patches):

Modified: subversion/branches/ra-git/tools/dist/templates/download.ezt
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/dist/templates/download.ezt?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/dist/templates/download.ezt (original)
+++ subversion/branches/ra-git/tools/dist/templates/download.ezt Wed Nov  7 12:30:06 2018
@@ -2,12 +2,14 @@
 <table class="centered">
 <tr>
   <th>File</th>
-  <th>Checksum (SHA1)</th>
+  <th>Checksum (SHA512)</th>
   <th>Signatures</th>
 </tr>
 [for fileinfo]<tr>
   <td><a href="[[]preferred]subversion/[fileinfo.filename]">[fileinfo.filename]</a></td>
-  <td class="checksum">[fileinfo.sha1]</td>
-  <td>[<a href="http://www.apache.org/dist/subversion/[fileinfo.filename].asc">PGP</a>]</td>
+  <!-- The sha512 line does not have a class="checksum" since the link needn't
+       be rendered in monospace. -->
+  <td>[<a href="https://www.apache.org/dist/subversion/[fileinfo.filename].sha512">SHA-512</a>]</td>
+  <td>[<a href="https://www.apache.org/dist/subversion/[fileinfo.filename].asc">PGP</a>]</td>
 </tr>[end]
 </table>

Modified: subversion/branches/ra-git/tools/dist/templates/rc-news.ezt
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/dist/templates/rc-news.ezt?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/dist/templates/rc-news.ezt (original)
+++ subversion/branches/ra-git/tools/dist/templates/rc-news.ezt Wed Nov  7 12:30:06 2018
@@ -8,10 +8,10 @@
    release is not intended for production use, but is provided as a milestone
    to encourage wider testing and feedback from intrepid users and maintainers.
    Please see the
-   <a href="">release
+   <a href="[announcement_url]">release
    announcement</a> for more information about this release, and the
    <a href="/docs/release-notes/[major-minor].html">release notes</a> and 
-   <a href="http://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES"> 
+   <a href="https://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES"> 
    change log</a> for information about what will eventually be
    in the [version_base] release.</p> 
  

Modified: subversion/branches/ra-git/tools/dist/templates/rc-release-ann.ezt
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/dist/templates/rc-release-ann.ezt?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/dist/templates/rc-release-ann.ezt (original)
+++ subversion/branches/ra-git/tools/dist/templates/rc-release-ann.ezt Wed Nov  7 12:30:06 2018
@@ -1,32 +1,39 @@
+From: ...@apache.org
+To: announce@subversion.apache.org, users@subversion.apache.org, dev@subversion.apache.org, announce@apache.org
+Subject: [[]ANNOUNCE] Apache Subversion [version] released
+
 I'm happy to announce the release of Apache Subversion [version].
 Please choose the mirror closest to you by visiting:
 
-    http://subversion.apache.org/download.cgi#[anchor]
+    https://subversion.apache.org/download.cgi#[anchor]
+
+SHA-512 checksums are available at:
 
-The SHA1 checksums are:
+    https://www.apache.org/dist/subversion/subversion-[version].tar.bz2.sha512
+    https://www.apache.org/dist/subversion/subversion-[version].tar.gz.sha512
+    https://www.apache.org/dist/subversion/subversion-[version].zip.sha512
 
-[for sha1info]    [sha1info.sha1] [sha1info.filename]
-[end]
 PGP Signatures are available at:
 
-    http://www.apache.org/dist/subversion/subversion-[version].tar.bz2.asc
-    http://www.apache.org/dist/subversion/subversion-[version].tar.gz.asc
-    http://www.apache.org/dist/subversion/subversion-[version].zip.asc
+    https://www.apache.org/dist/subversion/subversion-[version].tar.bz2.asc
+    https://www.apache.org/dist/subversion/subversion-[version].tar.gz.asc
+    https://www.apache.org/dist/subversion/subversion-[version].zip.asc
 
 For this release, the following people have provided PGP signatures:
 
 [siginfo]
-This is a pre-release for what will eventually become Apache Subversion
-[major-minor-patch].  It may contain known issues, a complete list of
-[major-minor-patch]-blocking issues can be found here:
+This is a pre-release for what will eventually become version [major-minor-patch] of the
+Apache Subversion open source version control system.  It may contain known
+issues, a complete list of [major-minor-patch]-blocking issues can be found
+here:
 
-    http://subversion.tigris.org/issues/buglist.cgi?component=subversion&issue_status=NEW&issue_status=STARTED&issue_status=REOPENED&target_milestone=[major-minor-patch]
+    https://issues.apache.org/jira/issues/?jql=project%20%3D%20SVN%20AND%20resolution%20%3D%20Unresolved%20AND%20fixVersion%20%3D%20[major-minor-patch]%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC
 
 A pre-release means the Subversion developers feel that this release
 is ready for widespread testing by the community.  There are known issues
 (and unknown ones!), so please use it at your own risk, though we do
 encourage people to test this release thoroughly.  Of particular note, please
-remember than persistent data, such as the working copy or repository
+remember that persistent data, such as the working copy or repository
 formats may change before the final release, and there may not be an
 upgrade path from the pre-releases to the final.
 
@@ -46,13 +53,18 @@ end users please.
 
 Release notes for the [major-minor].x release series may be found at:
 
-    http://subversion.apache.org/docs/release-notes/[major-minor].html
+    https://subversion.apache.org/docs/release-notes/[major-minor].html
 
 You can find the list of changes between [version] and earlier versions at:
 
-    http://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES
+    https://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES
 
 Questions, comments, and bug reports to users@subversion.apache.org.
 
 Thanks,
 - The Subversion Team
+
+--
+To unsubscribe, please see:
+
+    https://subversion.apache.org/mailing-lists.html#unsubscribing

Modified: subversion/branches/ra-git/tools/dist/templates/stable-news.ezt
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/dist/templates/stable-news.ezt?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/dist/templates/stable-news.ezt (original)
+++ subversion/branches/ra-git/tools/dist/templates/stable-news.ezt Wed Nov  7 12:30:06 2018
@@ -5,12 +5,15 @@
 </h3> 
  
 <p>We are pleased to announce the release of Apache Subversion [version].
-   This is the most complete Subversion release to date, and we encourage
-   users of Subversion to upgrade as soon as reasonable.  Please see the
-   <a href=""
+[if-any is_recommended]   This is the most complete Subversion release to date, and we encourage
+   users of Subversion to upgrade as soon as reasonable.
+[else]   This is the most complete release of the [major-minor].x line to date,
+   and we encourage all users to upgrade as soon as reasonable.
+[end]   Please see the
+   <a href="[announcement_url]"
    >release announcement</a> and the
-   <a href="http://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES"
-   >change log</a> for more information about this release.</p> 
+   <a href="/docs/release-notes/[major-minor]"
+   >release notes</a> for more information about this release.</p> 
  
 <p>To get this release from the nearest mirror, please visit our
    <a href="/download.cgi#[anchor]">download page</a>.</p> 

Modified: subversion/branches/ra-git/tools/dist/templates/stable-release-ann.ezt
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/dist/templates/stable-release-ann.ezt?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/dist/templates/stable-release-ann.ezt (original)
+++ subversion/branches/ra-git/tools/dist/templates/stable-release-ann.ezt Wed Nov  7 12:30:06 2018
@@ -1,30 +1,52 @@
+From: ...@apache.org
+To: announce@subversion.apache.org, users@subversion.apache.org, dev@subversion.apache.org, announce@apache.org
+[if-any security]Cc: security@apache.org, oss-security@lists.openwall.com, bugtraq@securityfocus.com
+[end][if-any security]Subject: [[]SECURITY][[]ANNOUNCE] Apache Subversion [version] released
+[else]Subject: [[]ANNOUNCE] Apache Subversion [version] released
+[end]
 I'm happy to announce the release of Apache Subversion [version].
 Please choose the mirror closest to you by visiting:
 
-    http://subversion.apache.org/download.cgi#[anchor]
-
-The SHA1 checksums are:
+    https://subversion.apache.org/download.cgi#[anchor]
+[if-any dot-zero]
+This is a stable feature release of the Apache Subversion open source
+version control system.
+[else][if-any security]
+This is a stable bugfix and security release of the Apache Subversion
+open source version control system.
+[else]
+This is a stable bugfix release of the Apache Subversion open source
+version control system.
+[end][end]
+SHA-512 checksums are available at:
+
+    https://www.apache.org/dist/subversion/subversion-[version].tar.bz2.sha512
+    https://www.apache.org/dist/subversion/subversion-[version].tar.gz.sha512
+    https://www.apache.org/dist/subversion/subversion-[version].zip.sha512
 
-[for sha1info]    [sha1info.sha1] [sha1info.filename]
-[end]
 PGP Signatures are available at:
 
-    http://www.apache.org/dist/subversion/subversion-[version].tar.bz2.asc
-    http://www.apache.org/dist/subversion/subversion-[version].tar.gz.asc
-    http://www.apache.org/dist/subversion/subversion-[version].zip.asc
+    https://www.apache.org/dist/subversion/subversion-[version].tar.bz2.asc
+    https://www.apache.org/dist/subversion/subversion-[version].tar.gz.asc
+    https://www.apache.org/dist/subversion/subversion-[version].zip.asc
 
 For this release, the following people have provided PGP signatures:
 
 [siginfo]
 Release notes for the [major-minor].x release series may be found at:
 
-    http://subversion.apache.org/docs/release-notes/[major-minor].html
+    https://subversion.apache.org/docs/release-notes/[major-minor].html
 
 You can find the list of changes between [version] and earlier versions at:
 
-    http://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES
+    https://svn.apache.org/repos/asf/subversion/tags/[version]/CHANGES
 
 Questions, comments, and bug reports to users@subversion.apache.org.
 
 Thanks,
 - The Subversion Team
+
+--
+To unsubscribe, please see:
+
+    https://subversion.apache.org/mailing-lists.html#unsubscribing

Modified: subversion/branches/ra-git/tools/examples/ExampleAuthn.java
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/examples/ExampleAuthn.java?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/examples/ExampleAuthn.java (original)
+++ subversion/branches/ra-git/tools/examples/ExampleAuthn.java Wed Nov  7 12:30:06 2018
@@ -68,8 +68,11 @@ public class ExampleAuthn {
                              SSLServerCertFailures failures,
                              SSLServerCertInfo info,
                              boolean maySave) {
-          System.out.println("sslServerTrustPrompt not implemented!");
-          return SSLServerTrustResult.acceptTemporarily();
+          System.out.println("sslServerTrustPrompt");
+          System.out.println("(r)eject or (t)emporary?");
+          String s = System.console().readLine();
+          return s.equals("t") ? SSLServerTrustResult.acceptTemporarily()
+                               : SSLServerTrustResult.reject();
         }
 
         public SSLClientCertResult

Modified: subversion/branches/ra-git/tools/hook-scripts/mailer/mailer.conf.example
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/hook-scripts/mailer/mailer.conf.example?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/hook-scripts/mailer/mailer.conf.example (original)
+++ subversion/branches/ra-git/tools/hook-scripts/mailer/mailer.conf.example Wed Nov  7 12:30:06 2018
@@ -27,6 +27,10 @@
 #smtp_username = example
 #smtp_password = example
 
+# This option specifies whether to use SSL from the beginning of the SMTP
+# connection.
+#smtp_ssl = yes
+
 # --------------------------------------------------------------------------
 
 #

Modified: subversion/branches/ra-git/tools/hook-scripts/mailer/mailer.py
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/hook-scripts/mailer/mailer.py?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/hook-scripts/mailer/mailer.py (original)
+++ subversion/branches/ra-git/tools/hook-scripts/mailer/mailer.py Wed Nov  7 12:30:06 2018
@@ -71,16 +71,10 @@ _MIN_SVN_VERSION = [1, 5, 0]
 
 # Import the Subversion Python bindings, making sure they meet our
 # minimum version requirements.
-try:
-  import svn.fs
-  import svn.delta
-  import svn.repos
-  import svn.core
-except ImportError:
-  sys.stderr.write(
-    "You need version %s or better of the Subversion Python bindings.\n" \
-    % ".".join([str(x) for x in _MIN_SVN_VERSION]))
-  sys.exit(1)
+import svn.fs
+import svn.delta
+import svn.repos
+import svn.core
 if _MIN_SVN_VERSION > [svn.core.SVN_VER_MAJOR,
                        svn.core.SVN_VER_MINOR,
                        svn.core.SVN_VER_PATCH]:
@@ -291,7 +285,10 @@ class SMTPOutput(MailedOutput):
     self.write(self.mail_headers(group, params))
 
   def finish(self):
-    server = smtplib.SMTP(self.cfg.general.smtp_hostname)
+    if self.cfg.is_set('general.smtp_ssl') and self.cfg.general.smtp_ssl == 'yes':
+      server = smtplib.SMTP_SSL(self.cfg.general.smtp_hostname)
+    else:
+      server = smtplib.SMTP(self.cfg.general.smtp_hostname)
     if self.cfg.is_set('general.smtp_username'):
       server.login(self.cfg.general.smtp_username,
                    self.cfg.general.smtp_password)

Modified: subversion/branches/ra-git/tools/server-side/svnauthz.c
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/server-side/svnauthz.c?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/server-side/svnauthz.c (original)
+++ subversion/branches/ra-git/tools/server-side/svnauthz.c Wed Nov  7 12:30:06 2018
@@ -110,29 +110,34 @@ static svn_opt_subcommand_t
 /* Array of available subcommands.
  * The entire list must be terminated with an entry of nulls.
  */
-static const svn_opt_subcommand_desc2_t cmd_table[] =
+static const svn_opt_subcommand_desc3_t cmd_table[] =
 {
-  {"help", subcommand_help, {"?", "h"},
-   ("usage: svnauthz help [SUBCOMMAND...]\n\n"
-    "Describe the usage of this program or its subcommands.\n"),
+  {"help", subcommand_help, {"?", "h"}, {(
+    "usage: svnauthz help [SUBCOMMAND...]\n"
+    "\n"
+    "Describe the usage of this program or its subcommands.\n"
+    )},
    {0} },
-  {"validate", subcommand_validate, {0} /* no aliases */,
-   ("Checks the syntax of an authz file.\n"
+  {"validate", subcommand_validate, {0} /* no aliases */, {(
+    "Checks the syntax of an authz file.\n"
     "usage: 1. svnauthz validate TARGET\n"
-    "       2. svnauthz validate --transaction TXN REPOS_PATH FILE_PATH\n\n"
+    "       2. svnauthz validate --transaction TXN REPOS_PATH FILE_PATH\n"
+    "\n"
     "  1. Loads and validates the syntax of the authz file at TARGET.\n"
     "     TARGET can be a path to a file or an absolute file:// URL to an authz\n"
-    "     file in a repository, but cannot be a repository relative URL (^/).\n\n"
+    "     file in a repository, but cannot be a repository relative URL (^/).\n"
+    "\n"
     "  2. Loads and validates the syntax of the authz file at FILE_PATH in the\n"
-    "     transaction TXN in the repository at REPOS_PATH.\n\n"
+    "     transaction TXN in the repository at REPOS_PATH.\n"
+    "\n"
     "Returns:\n"
     "    0   when syntax is OK.\n"
     "    1   when syntax is invalid.\n"
     "    2   operational error\n"
-    ),
+    )},
    {'t'} },
-  {"accessof", subcommand_accessof, {0} /* no aliases */,
-   ("Print or test the permissions set by an authz file.\n"
+  {"accessof", subcommand_accessof, {0} /* no aliases */, {(
+    "Print or test the permissions set by an authz file.\n"
     "usage: 1. svnauthz accessof TARGET\n"
     "       2. svnauthz accessof -t TXN REPOS_PATH FILE_PATH\n"
     "\n"
@@ -159,10 +164,10 @@ static const svn_opt_subcommand_desc2_t
     "    1   when syntax is invalid.\n"
     "    2   operational error\n"
     "    3   when '--is' argument doesn't match\n"
-    ),
+    )},
    {'t', svnauthz__username, svnauthz__path, svnauthz__repos, svnauthz__is,
     svnauthz__groups_file, 'R'} },
-  { NULL, NULL, {0}, NULL, {0} }
+  { NULL, NULL, {0}, {NULL}, {0} }
 };
 
 static svn_error_t *
@@ -171,11 +176,14 @@ subcommand_help(apr_getopt_t *os, void *
   struct svnauthz_opt_state *opt_state = baton;
   const char *header =
     ("general usage: svnauthz SUBCOMMAND TARGET [ARGS & OPTIONS ...]\n"
-     "               " SVNAUTHZ_COMPAT_NAME " TARGET\n\n"
+     "               " SVNAUTHZ_COMPAT_NAME " TARGET\n"
+    "\n"
      "If the command name starts with '" SVNAUTHZ_COMPAT_NAME "', runs in\n"
-     "pre-1.8 compatibility mode: run the 'validate' subcommand on TARGET.\n\n"
+     "pre-1.8 compatibility mode: run the 'validate' subcommand on TARGET.\n"
+    "\n"
      "Type 'svnauthz help <subcommand>' for help on a specific subcommand.\n"
-     "Type 'svnauthz --version' to see the program version.\n\n"
+     "Type 'svnauthz --version' to see the program version.\n"
+    "\n"
      "Available subcommands:\n");
 
   const char *fs_desc_start
@@ -186,7 +194,7 @@ subcommand_help(apr_getopt_t *os, void *
   version_footer = svn_stringbuf_create(fs_desc_start, pool);
   SVN_ERR(svn_fs_print_modules(version_footer, pool));
 
-  SVN_ERR(svn_opt_print_help4(os, "svnauthz",
+  SVN_ERR(svn_opt_print_help5(os, "svnauthz",
                               opt_state ? opt_state->version : FALSE,
                               FALSE, /* quiet */
                               FALSE, /* verbose */
@@ -275,9 +283,9 @@ get_authz(svn_authz_t **authz, struct sv
                               opt_state->txn, pool);
 
   /* Else */
-  return svn_repos_authz_read2(authz, opt_state->authz_file,
+  return svn_repos_authz_read3(authz, opt_state->authz_file,
                                opt_state->groups_file,
-                               TRUE, pool);
+                               TRUE, NULL, pool, pool);
 }
 
 static svn_error_t *
@@ -459,7 +467,7 @@ sub_main(int *exit_code, int argc, const
 {
   svn_error_t *err;
 
-  const svn_opt_subcommand_desc2_t *subcommand = NULL;
+  const svn_opt_subcommand_desc3_t *subcommand = NULL;
   struct svnauthz_opt_state opt_state = { 0 };
   apr_getopt_t *os;
   apr_array_header_t *received_opts;
@@ -545,9 +553,9 @@ sub_main(int *exit_code, int argc, const
     {
       /* Pre 1.8 compatibility mode. */
       if (argc == 1) /* No path argument */
-        subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
+        subcommand = svn_opt_get_canonical_subcommand3(cmd_table, "help");
       else
-        subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "validate");
+        subcommand = svn_opt_get_canonical_subcommand3(cmd_table, "validate");
     }
 
   /* If the user asked for help, then the rest of the arguments are
@@ -555,7 +563,7 @@ sub_main(int *exit_code, int argc, const
      just typos/mistakes.  Whatever the case, the subcommand to
      actually run is subcommand_help(). */
   if (opt_state.help)
-    subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
+    subcommand = svn_opt_get_canonical_subcommand3(cmd_table, "help");
 
   if (subcommand == NULL)
     {
@@ -564,8 +572,8 @@ sub_main(int *exit_code, int argc, const
           if (opt_state.version)
             {
               /* Use the "help" subcommand to handle the "--version" option. */
-              static const svn_opt_subcommand_desc2_t pseudo_cmd =
-                { "--version", subcommand_help, {0}, "",
+              static const svn_opt_subcommand_desc3_t pseudo_cmd =
+                { "--version", subcommand_help, {0}, {""},
                   {svnauthz__version /* must accept its own option */ } };
 
               subcommand = &pseudo_cmd;
@@ -581,19 +589,18 @@ sub_main(int *exit_code, int argc, const
         }
       else
         {
-          const char *first_arg = os->argv[os->ind++];
-          subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
+          const char *first_arg;
+
+          SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
+                                          pool));
+          subcommand = svn_opt_get_canonical_subcommand3(cmd_table, first_arg);
           if (subcommand == NULL)
             {
-              const char *first_arg_utf8;
-
               os->ind++;
-              SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
-                                                  first_arg, pool));
               svn_error_clear(
                 svn_cmdline_fprintf(stderr, pool,
                                     ("Unknown subcommand: '%s'\n"),
-                                    first_arg_utf8));
+                                    first_arg));
               SVN_ERR(subcommand_help(NULL, NULL, pool));
               *exit_code = EXIT_FAILURE;
               return SVN_NO_ERROR;
@@ -659,11 +666,11 @@ sub_main(int *exit_code, int argc, const
       if (opt_id == 'h' || opt_id == '?')
         continue;
 
-      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+      if (! svn_opt_subcommand_takes_option4(subcommand, opt_id, NULL))
         {
           const char *optstr;
           const apr_getopt_option_t *badopt =
-            svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
+            svn_opt_get_option_from_code3(opt_id, options_table, subcommand,
                                           pool);
           svn_opt_format_option(&optstr, badopt, FALSE, pool);
           if (subcommand->name[0] == '-')

Modified: subversion/branches/ra-git/tools/server-side/svnpubsub/svnwcsub.py
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/tools/server-side/svnpubsub/svnwcsub.py?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/tools/server-side/svnpubsub/svnwcsub.py (original)
+++ subversion/branches/ra-git/tools/server-side/svnpubsub/svnwcsub.py Wed Nov  7 12:30:06 2018
@@ -32,7 +32,7 @@
 
 # TODO:
 # - bulk update at startup time to avoid backlog warnings
-# - fold BDEC into Daemon
+# - fold BigDoEverythingClasss ("BDEC") into Daemon
 # - fold WorkingCopy._get_match() into __init__
 # - remove wc_ready(). assume all WorkingCopy instances are usable.
 #   place the instances into .watch at creation. the .update_applies()

Modified: subversion/branches/ra-git/win-tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/win-tests.py?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/win-tests.py (original)
+++ subversion/branches/ra-git/win-tests.py Wed Nov  7 12:30:06 2018
@@ -113,6 +113,7 @@ def _usage_exit():
   print("  --config-file          : Configuration file for tests")
   print("  --fsfs-sharding        : Specify shard size (for fsfs)")
   print("  --fsfs-packing         : Run 'svnadmin pack' automatically")
+  print("  --fsfs-compression=VAL : Set compression type to VAL (for fsfs)")
   print("  -q, --quiet            : Deprecated; this is the default.")
   print("                           Use --set-log-level instead.")
 
@@ -144,6 +145,7 @@ opts, args = my_getopt(sys.argv[1:], 'hr
                         'log-to-stdout', 'mode-filter=', 'milestone-filter=',
                         'ssl-cert=', 'exclusive-wc-locks', 'memcached-server=',
                         'skip-c-tests', 'dump-load-cross-check', 'memcached-dir=',
+                        'fsfs-compression=',
                         ])
 if len(args) > 1:
   print('Warning: non-option arguments after the first one will be ignored')
@@ -189,6 +191,8 @@ memcached_server = None
 memcached_dir = None
 skip_c_tests = None
 dump_load_cross_check = None
+fsfs_compression = None
+fsfs_dir_deltification = None
 
 for opt, val in opts:
   if opt in ('-h', '--help'):
@@ -283,6 +287,10 @@ for opt, val in opts:
   elif opt == '--memcached-dir':
     memcached_dir = val
     run_memcached = 1
+  elif opt == '--fsfs-compression':
+    fsfs_compression = val
+  elif opt == '--fsfs-dir-deltification':
+    fsfs_dir_deltification = val
 
 # Calculate the source and test directory names
 abs_srcdir = os.path.abspath("")
@@ -1012,6 +1020,7 @@ create_target_dir(CMDLINE_TEST_SCRIPT_NA
 # Ensure the tests directory is correctly cased
 abs_builddir = fix_case(abs_builddir)
 
+failed = None
 daemon = None
 memcached = None
 # Run the tests
@@ -1107,6 +1116,8 @@ if not test_javahl and not test_swig:
   opts.memcached_server = memcached_server
   opts.skip_c_tests = skip_c_tests
   opts.dump_load_cross_check = dump_load_cross_check
+  opts.fsfs_compression = fsfs_compression
+  opts.fsfs_dir_deltification = fsfs_dir_deltification
   th = run_tests.TestHarness(abs_srcdir, abs_builddir,
                              log_file, fail_log_file, opts)
   old_cwd = os.getcwd()
@@ -1324,6 +1335,10 @@ elif test_swig == 'ruby':
       print('[Test runner reported failure]')
       failed = True
 
+elif test_swig:
+  print('Unknown Swig binding type: ' + str(test_swig))
+  failed = True
+
 # Stop service daemon, if any
 if daemon:
   del daemon



Mime
View raw message