Modified: 
subversion/branches/multi-wc-format/tools/hook-scripts/mailer/mailer.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/tools/hook-scripts/mailer/mailer.py?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/tools/hook-scripts/mailer/mailer.py 
(original)
+++ subversion/branches/multi-wc-format/tools/hook-scripts/mailer/mailer.py Fri 
Jan 14 14:01:45 2022
@@ -46,25 +46,21 @@
 
 import os
 import sys
-try:
-  # Python >=3.0
+if sys.hexversion >= 0x3000000:
+  PY3 = True
   import configparser
-  from urllib.parse import quote as urllib_parse_quote
-except ImportError:
-  # Python <3.0
+  from urllib.parse import quote as _url_quote
+else:
+  PY3 = False
   import ConfigParser as configparser
-  from urllib import quote as urllib_parse_quote
+  from urllib import quote as  _url_quote
 import time
 import subprocess
-if sys.version_info[0] >= 3:
-  # Python >=3.0
-  from io import StringIO
-else:
-  # Python <3.0
-  from cStringIO import StringIO
+from io import BytesIO
 import smtplib
 import re
 import tempfile
+import codecs
 
 # Minimal version of Subversion's bindings required
 _MIN_SVN_VERSION = [1, 5, 0]
@@ -83,6 +79,28 @@ if _MIN_SVN_VERSION > [svn.core.SVN_VER_
     % ".".join([str(x) for x in _MIN_SVN_VERSION]))
   sys.exit(1)
 
+# Absorb difference between Python 2 and Python >= 3
+if PY3:
+  def to_bytes(x):
+    return x.encode('utf-8')
+
+  def to_str(x):
+    return x.decode('utf-8')
+
+  # We never use sys.stdin nor sys.stdout TextIOwrapper.
+  _stdin = sys.stdin.buffer
+  _stdout = sys.stdout.buffer
+else:
+  # Python 2
+  def to_bytes(x):
+    return x
+
+  def to_str(x):
+    return x
+
+  _stdin = sys.stdin
+  _stdout = sys.stdout
+
 
 SEPARATOR = '=' * 78
 
@@ -101,7 +119,10 @@ def main(pool, cmd, config_fname, repos_
     revision = int(cmd_args[0])
     author = cmd_args[1]
     propname = cmd_args[2]
-    action = (cmd == 'propchange2' and cmd_args[3] or 'A')
+    if cmd == 'propchange2' and cmd_args[3]:
+      action = cmd_args[3]
+    else:
+      action = 'A'
     repos = Repository(repos_dir, revision, pool)
     # Override the repos revision author with the author of the propchange
     repos.author = author
@@ -123,11 +144,11 @@ def main(pool, cmd, config_fname, repos_
   else:
     raise UnknownSubcommand(cmd)
 
-  messenger.generate()
+  return messenger.generate()
 
 
 def remove_leading_slashes(path):
-  while path and path[0] == '/':
+  while path and path[0:1] == b'/':
     path = path[1:]
   return path
 
@@ -158,8 +179,16 @@ class OutputBase:
     except ValueError:
       truncate_subject = 0
 
-    if truncate_subject and len(subject) > truncate_subject:
-      subject = subject[:(truncate_subject - 3)] + "..."
+    # truncate subject as UTF-8 string.
+    # Note: there still exists an issue on combining characters.
+    if truncate_subject:
+      bsubject = to_bytes(subject)
+      if len(bsubject) > truncate_subject:
+        idx = truncate_subject - 2
+        while b'\x80' <= bsubject[idx-1:idx] <= b'\xbf':
+          idx -= 1
+        subject = to_str(bsubject[:idx-1]) + "..."
+
     return subject
 
   def start(self, group, params):
@@ -177,11 +206,15 @@ class OutputBase:
     representation."""
     raise NotImplementedError
 
-  def write(self, output):
+  def write_binary(self, output):
     """Override this method.
-    Append the literal text string OUTPUT to the output representation."""
+    Append the binary data OUTPUT to the output representation."""
     raise NotImplementedError
 
+  def write(self, output):
+    """Append the literal text string OUTPUT to the output representation."""
+    return self.write_binary(to_bytes(output))
+
   def run(self, cmd):
     """Override this method, if the default implementation is not sufficient.
     Execute CMD, writing the stdout produced to the output representation."""
@@ -192,7 +225,7 @@ class OutputBase:
 
     buf = pipe_ob.stdout.read(self._CHUNKSIZE)
     while buf:
-      self.write(buf)
+      self.write_binary(buf)
       buf = pipe_ob.stdout.read(self._CHUNKSIZE)
 
     # wait on the child so we don't end up with a billion zombies
@@ -234,7 +267,7 @@ class MailedOutput(OutputBase):
     # Return the result of splitting HDR into tokens (on space
     # characters), encoding (per RFC2047) each token as necessary, and
     # slapping 'em back to together again.
-    from email.Header import Header
+    from email.header import Header
 
     def _maybe_encode_header(hdr_token):
       try:
@@ -246,7 +279,7 @@ class MailedOutput(OutputBase):
     return ' '.join(map(_maybe_encode_header, hdr.split()))
 
   def mail_headers(self, group, params):
-    from email import Utils
+    from email import utils
 
     subject  = self._rfc2047_encode(self.make_subject(group, params))
     from_hdr = self._rfc2047_encode(self.from_addr)
@@ -265,7 +298,7 @@ class MailedOutput(OutputBase):
            'X-Svn-Commit-Revision: %d\n' \
            'X-Svn-Commit-Repository: %s\n' \
            % (from_hdr, to_hdr, subject,
-              Utils.formatdate(), Utils.make_msgid(), group,
+              utils.formatdate(), utils.make_msgid(), group,
               self.repos.author or 'no_author', self.repos.rev,
               os.path.basename(self.repos.repos_dir))
     if self.reply_to:
@@ -279,21 +312,79 @@ class SMTPOutput(MailedOutput):
   def start(self, group, params):
     MailedOutput.start(self, group, params)
 
-    self.buffer = StringIO()
-    self.write = self.buffer.write
+    self.buffer = BytesIO()
+    self.write_binary = self.buffer.write
 
     self.write(self.mail_headers(group, params))
 
   def finish(self):
-    if self.cfg.is_set('general.smtp_ssl') and self.cfg.general.smtp_ssl == 
'yes':
-      server = smtplib.SMTP_SSL(self.cfg.general.smtp_hostname)
+    """
+    Send email via SMTP or SMTP_SSL, logging in if username is
+    specified.
+
+    Errors such as invalid recipient, which affect a particular email,
+    are reported to stderr and raise MessageSendFailure. If the caller
+    has other emails to send, it may continue doing so.
+
+    Errors caused by bad configuration, such as login failures, for
+    which too many occurrences could lead to SMTP server lockout, are
+    reported to stderr and re-raised. These should be considered fatal
+    (to minimize the chances of said lockout).
+    """
+
+    if self.cfg.is_set('general.smtp_port'):
+       smtp_port = self.cfg.general.smtp_port
     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)
-    server.sendmail(self.from_addr, self.to_addrs, self.buffer.getvalue())
-    server.quit()
+       smtp_port = 0
+    try:
+      if self.cfg.is_set('general.smtp_ssl') and self.cfg.general.smtp_ssl == 
'yes':
+        server = smtplib.SMTP_SSL(self.cfg.general.smtp_hostname, smtp_port)
+      else:
+        server = smtplib.SMTP(self.cfg.general.smtp_hostname, smtp_port)
+    except Exception as detail:
+      sys.stderr.write("mailer.py: Failed to instantiate SMTP object: %s\n" % 
(detail,))
+      # Any error to instantiate is fatal
+      raise
+
+    try:
+      if self.cfg.is_set('general.smtp_username'):
+        try:
+          server.login(self.cfg.general.smtp_username,
+                       self.cfg.general.smtp_password)
+        except smtplib.SMTPException as detail:
+          sys.stderr.write("mailer.py: SMTP login failed with username %s 
and/or password: %s\n"
+                           % (self.cfg.general.smtp_username, detail,))
+          # Any error at login is fatal
+          raise
+
+      server.sendmail(self.from_addr, self.to_addrs, self.buffer.getvalue())
+
+    ### TODO: 'raise .. from' is Python 3+. When we convert this
+    ###       script to Python 3, uncomment 'from detail' below
+    ###       (2 instances):
+
+    except smtplib.SMTPRecipientsRefused as detail:
+      sys.stderr.write("mailer.py: SMTP recipient(s) refused: %s: %s\n"
+                           % (self.to_addrs, detail,))
+      raise MessageSendFailure ### from detail
+
+    except smtplib.SMTPSenderRefused as detail:
+      sys.stderr.write("mailer.py: SMTP sender refused: %s: %s\n"
+                           % (self.from_addr, detail,))
+      raise MessageSendFailure ### from detail
+
+    except smtplib.SMTPException as detail:
+      # All other errors are fatal; this includes:
+      # SMTPHeloError, SMTPDataError, SMTPNotSupportedError
+      sys.stderr.write("mailer.py: SMTP error occurred: %s\n" % (detail,))
+      raise
+
+    finally:
+      try:
+        server.quit()
+      except smtplib.SMTPException as detail:
+        sys.stderr.write("mailer.py: Error occurred during SMTP session 
cleanup: %s\n"
+                             % (detail,))
 
 
 class StandardOutput(OutputBase):
@@ -301,7 +392,7 @@ class StandardOutput(OutputBase):
 
   def __init__(self, cfg, repos, prefix_param):
     OutputBase.__init__(self, cfg, repos, prefix_param)
-    self.write = sys.stdout.write
+    self.write_binary = _stdout.write
 
   def start(self, group, params):
     self.write("Group: " + (group or "defaults") + "\n")
@@ -310,6 +401,12 @@ class StandardOutput(OutputBase):
   def finish(self):
     pass
 
+  if (PY3 and (codecs.lookup(sys.stdout.encoding) != codecs.lookup('utf-8'))):
+    def write(self, output):
+      """Write text as *default* encoding string"""
+      return self.write_binary(output.encode(sys.stdout.encoding,
+                                             'backslashreplace'))
+
 
 class PipeOutput(MailedOutput):
   "Deliver a mail message to an MTA via a pipe."
@@ -330,7 +427,7 @@ class PipeOutput(MailedOutput):
     # construct the pipe for talking to the mailer
     self.pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                                  close_fds=sys.platform != "win32")
-    self.write = self.pipe.stdin.write
+    self.write_binary = self.pipe.stdin.write
 
     # start writing out the mail message
     self.write(self.mail_headers(group, params))
@@ -371,7 +468,7 @@ class Commit(Messenger):
 
     self.changelist = sorted(editor.get_changes().items())
 
-    log = repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or ''
+    log = to_str(repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or b'')
 
     # collect the set of groups and the unique sets of params for the options
     self.groups = { }
@@ -390,6 +487,7 @@ class Commit(Messenger):
     # figure out the changed directories
     dirs = { }
     for path, change in self.changelist:
+      path = to_str(path)
       if change.item_kind == svn.core.svn_node_dir:
         dirs[path] = None
       else:
@@ -421,21 +519,26 @@ class Commit(Messenger):
     ### rather than rebuilding it each time.
 
     subpool = svn.core.svn_pool_create(self.pool)
+    ret = 0
 
     # build a renderer, tied to our output stream
     renderer = TextCommitRenderer(self.output)
 
-    for (group, param_tuple), (params, paths) in self.groups.items():
-      self.output.start(group, params)
-
-      # generate the content for this group and set of params
-      generate_content(renderer, self.cfg, self.repos, self.changelist,
-                       group, params, paths, subpool)
+    for (group, param_tuple), (params, paths) in sorted(self.groups.items()):
+      try:
+        self.output.start(group, params)
 
-      self.output.finish()
+        # generate the content for this group and set of params
+        generate_content(renderer, self.cfg, self.repos, self.changelist,
+                         group, params, paths, subpool)
+
+        self.output.finish()
+      except MessageSendFailure:
+        ret = 1
       svn.core.svn_pool_clear(subpool)
 
     svn.core.svn_pool_destroy(subpool)
+    return ret
 
 
 class PropChange(Messenger):
@@ -456,35 +559,40 @@ class PropChange(Messenger):
 
   def generate(self):
     actions = { 'A': 'added', 'M': 'modified', 'D': 'deleted' }
+    ret = 0
     for (group, param_tuple), params in self.groups.items():
-      self.output.start(group, params)
-      self.output.write('Author: %s\n'
-                        'Revision: %s\n'
-                        'Property Name: %s\n'
-                        'Action: %s\n'
-                        '\n'
-                        % (self.author, self.repos.rev, self.propname,
-                           actions.get(self.action, 'Unknown (\'%s\')' \
-                                       % self.action)))
-      if self.action == 'A' or self.action not in actions:
-        self.output.write('Property value:\n')
-        propvalue = self.repos.get_rev_prop(self.propname)
-        self.output.write(propvalue)
-      elif self.action == 'M':
-        self.output.write('Property diff:\n')
-        tempfile1 = tempfile.NamedTemporaryFile()
-        tempfile1.write(sys.stdin.read())
-        tempfile1.flush()
-        tempfile2 = tempfile.NamedTemporaryFile()
-        tempfile2.write(self.repos.get_rev_prop(self.propname))
-        tempfile2.flush()
-        self.output.run(self.cfg.get_diff_cmd(group, {
-          'label_from' : 'old property value',
-          'label_to' : 'new property value',
-          'from' : tempfile1.name,
-          'to' : tempfile2.name,
-          }))
-      self.output.finish()
+      try:
+        self.output.start(group, params)
+        self.output.write('Author: %s\n'
+                          'Revision: %s\n'
+                          'Property Name: %s\n'
+                          'Action: %s\n'
+                          '\n'
+                          % (self.author, self.repos.rev, self.propname,
+                             actions.get(self.action, 'Unknown (\'%s\')' \
+                                         % self.action)))
+        if self.action == 'A' or self.action not in actions:
+          self.output.write('Property value:\n')
+          propvalue = self.repos.get_rev_prop(self.propname)
+          self.output.write(propvalue)
+        elif self.action == 'M':
+          self.output.write('Property diff:\n')
+          tempfile1 = tempfile.NamedTemporaryFile()
+          tempfile1.write(_stdin.read())
+          tempfile1.flush()
+          tempfile2 = tempfile.NamedTemporaryFile()
+          tempfile2.write(self.repos.get_rev_prop(self.propname))
+          tempfile2.flush()
+          self.output.run(self.cfg.get_diff_cmd(group, {
+            'label_from' : 'old property value',
+            'label_to' : 'new property value',
+            'from' : tempfile1.name,
+            'to' : tempfile2.name,
+            }))
+        self.output.finish()
+      except MessageSendFailure:
+        ret = 1
+    return ret
 
 
 def get_commondir(dirlist):
@@ -532,7 +640,7 @@ class Lock(Messenger):
                         or 'unlock_subject_prefix'))
 
     # read all the locked paths from STDIN and strip off the trailing newlines
-    self.dirlist = [x.rstrip() for x in sys.stdin.readlines()]
+    self.dirlist = [to_str(x).rstrip() for x in _stdin.readlines()]
 
     # collect the set of groups and the unique sets of params for the options
     self.groups = { }
@@ -561,24 +669,30 @@ class Lock(Messenger):
     # The lock comment is the same for all paths, so we can just pull
     # the comment for the first path in the dirlist and cache it.
     self.lock = svn.fs.svn_fs_get_lock(self.repos.fs_ptr,
-                                       self.dirlist[0], self.pool)
+                                       to_bytes(self.dirlist[0]),
+                                       self.pool)
 
   def generate(self):
-    for (group, param_tuple), (params, paths) in self.groups.items():
-      self.output.start(group, params)
-
-      self.output.write('Author: %s\n'
-                        '%s paths:\n' %
-                        (self.author, self.do_lock and 'Locked' or 'Unlocked'))
-
-      self.dirlist.sort()
-      for dir in self.dirlist:
-        self.output.write('   %s\n\n' % dir)
-
-      if self.do_lock:
-        self.output.write('Comment:\n%s\n' % (self.lock.comment or ''))
+    ret = 0
+    for (group, param_tuple), (params, paths) in sorted(self.groups.items()):
+      try:
+        self.output.start(group, params)
 
-      self.output.finish()
+        self.output.write('Author: %s\n'
+                          '%s paths:\n' %
+                          (self.author, self.do_lock and 'Locked' or 
'Unlocked'))
+
+        self.dirlist.sort()
+        for dir in self.dirlist:
+          self.output.write('   %s\n\n' % dir)
+
+        if self.do_lock:
+          self.output.write('Comment:\n%s\n' % (self.lock.comment or ''))
+
+        self.output.finish()
+      except MessageSendFailure:
+        ret = 1
+    return ret
 
 
 class DiffSelections:
@@ -629,9 +743,9 @@ class DiffURLSelections:
     # parameters for the configuration module, otherwise we may get
     # KeyError exceptions.
     params = self.params.copy()
-    params['path'] = change.path and urllib_parse_quote(change.path) or None
-    params['base_path'] = change.base_path and 
urllib_parse_quote(change.base_path) \
-                          or None
+    params['path'] = _url_quote(change.path) if change.path else None
+    params['base_path'] = (_url_quote(change.base_path)
+                           if change.base_path else None)
     params['rev'] = repos_rev
     params['base_rev'] = change.base_rev
 
@@ -685,7 +799,7 @@ def generate_content(renderer, cfg, repo
     author=repos.author,
     date=date,
     rev=repos.rev,
-    log=repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or '',
+    log=to_str(repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or b''),
     commit_url=commit_url,
     added_data=generate_list('A', changelist, paths, True),
     replaced_data=generate_list('R', changelist, paths, True),
@@ -793,7 +907,9 @@ class DiffGenerator:
 
       # figure out if/how to generate a diff
 
-      base_path = remove_leading_slashes(change.base_path)
+      base_path_bytes = remove_leading_slashes(change.base_path)
+      base_path = (to_str(base_path_bytes)
+                   if base_path_bytes is not None else None)
       if change.action == svn.repos.CHANGE_ACTION_DELETE:
         # it was delete.
         kind = 'D'
@@ -804,7 +920,7 @@ class DiffGenerator:
         # show the diff?
         if self.diffsels.delete:
           diff = svn.fs.FileDiff(self.repos.get_root(change.base_rev),
-                                 base_path, None, None, self.pool)
+                                 base_path_bytes, None, None, self.pool)
 
           label1 = '%s\t%s\t(r%s)' % (base_path, self.date, change.base_rev)
           label2 = '/dev/null\t00:00:00 1970\t(deleted)'
@@ -825,13 +941,13 @@ class DiffGenerator:
             # show the diff?
             if self.diffsels.modify:
               diff = svn.fs.FileDiff(self.repos.get_root(change.base_rev),
-                                     base_path,
+                                     base_path_bytes,
                                      self.repos.root_this, change.path,
                                      self.pool)
-              label1 = '%s\t%s\t(r%s, copy source)' \
-                       % (base_path, base_date, change.base_rev)
-              label2 = '%s\t%s\t(r%s)' \
-                       % (change.path, self.date, self.repos.rev)
+              label1 = ('%s\t%s\t(r%s, copy source)'
+                        % (base_path, base_date, change.base_rev))
+              label2 = ('%s\t%s\t(r%s)'
+                        % (to_str(change.path), self.date, self.repos.rev))
               singular = False
           else:
             # this file was copied.
@@ -839,11 +955,12 @@ class DiffGenerator:
             if self.diffsels.copy:
               diff = svn.fs.FileDiff(None, None, self.repos.root_this,
                                      change.path, self.pool)
-              label1 = '/dev/null\t00:00:00 1970\t' \
-                       '(empty, because file is newly added)'
-              label2 = '%s\t%s\t(r%s, copy of r%s, %s)' \
-                       % (change.path, self.date, self.repos.rev, \
-                          change.base_rev, base_path)
+              label1 = ('/dev/null\t00:00:00 1970\t'
+                        '(empty, because file is newly added)')
+              label2 = ('%s\t%s\t(r%s, copy of r%s, %s)'
+                        % (to_str(change.path),
+                           self.date, self.repos.rev, change.base_rev,
+                           base_path))
               singular = False
         else:
           # the file was added.
@@ -859,7 +976,7 @@ class DiffGenerator:
             label1 = '/dev/null\t00:00:00 1970\t' \
                      '(empty, because file is newly added)'
             label2 = '%s\t%s\t(r%s)' \
-                     % (change.path, self.date, self.repos.rev)
+                     % (to_str(change.path), self.date, self.repos.rev)
             singular = True
 
       elif not change.text_changed:
@@ -881,7 +998,7 @@ class DiffGenerator:
           label1 = '%s\t%s\t(r%s)' \
                    % (base_path, base_date, change.base_rev)
           label2 = '%s\t%s\t(r%s)' \
-                   % (change.path, self.date, self.repos.rev)
+                   % (to_str(change.path), self.date, self.repos.rev)
           singular = False
 
       if diff:
@@ -904,7 +1021,7 @@ class DiffGenerator:
       # return a data item for this diff
       return _data(
         path=change.path,
-        base_path=base_path,
+        base_path=base_path_bytes,
         base_rev=change.base_rev,
         diff=diff,
         diff_url=diff_url,
@@ -1071,7 +1188,7 @@ class TextCommitRenderer:
           props = '   (props changed)'
       else:
         props = ''
-      w('   %s%s%s\n' % (d.path, is_dir, props))
+      w('   %s%s%s\n' % (to_str(d.path), is_dir, props))
       if d.copied:
         if is_dir:
           text = ''
@@ -1080,7 +1197,7 @@ class TextCommitRenderer:
         else:
           text = ' unchanged'
         w('      - copied%s from r%d, %s%s\n'
-          % (text, d.base_rev, d.base_path, is_dir))
+          % (text, d.base_rev, to_str(d.base_path), is_dir))
 
   def _render_diffs(self, diffs, section_header):
     """Render diffs. Write the SECTION_HEADER if there are actually
@@ -1097,18 +1214,20 @@ class TextCommitRenderer:
         w(section_header)
         section_header_printed = True
       if diff.kind == 'D':
-        w('\nDeleted: %s\n' % diff.base_path)
+        w('\nDeleted: %s\n' % to_str(diff.base_path))
       elif diff.kind == 'A':
-        w('\nAdded: %s\n' % diff.path)
+        w('\nAdded: %s\n' % to_str(diff.path))
       elif diff.kind == 'C':
         w('\nCopied: %s (from r%d, %s)\n'
-          % (diff.path, diff.base_rev, diff.base_path))
+          % (to_str(diff.path), diff.base_rev,
+             to_str(diff.base_path)))
       elif diff.kind == 'W':
         w('\nCopied and modified: %s (from r%d, %s)\n'
-          % (diff.path, diff.base_rev, diff.base_path))
+          % (to_str(diff.path), diff.base_rev,
+             to_str(diff.base_path)))
       else:
         # kind == 'M'
-        w('\nModified: %s\n' % diff.path)
+        w('\nModified: %s\n' % to_str(diff.path))
 
       if diff.diff_url:
         w('URL: %s\n' % diff.diff_url)
@@ -1125,8 +1244,9 @@ class TextCommitRenderer:
           w('Binary file (source and/or target). No diff available.\n')
         continue
 
+      wb = self.output.write_binary
       for line in diff.content:
-        w(line.raw)
+        wb(line.raw)
 
 
 class Repository:
@@ -1145,6 +1265,8 @@ class Repository:
     self.root_this = self.get_root(rev)
 
     self.author = self.get_rev_prop(svn.core.SVN_PROP_REVISION_AUTHOR)
+    if self.author is not None:
+      self.author = to_str(self.author)
 
   def get_rev_prop(self, propname, rev = None):
     if not rev:
@@ -1353,9 +1475,9 @@ class Config:
     "Return the path's associated groups."
     groups = []
     for group, pattern, exclude_pattern, repos_params, search_logmsg_re in 
self._group_re:
-      match = pattern.match(path)
+      match = pattern.match(to_str(path))
       if match:
-        if exclude_pattern and exclude_pattern.match(path):
+        if exclude_pattern and exclude_pattern.match(to_str(path)):
           continue
         params = repos_params.copy()
         params.update(match.groupdict())
@@ -1394,6 +1516,8 @@ class UnknownMappingSpec(Exception):
   pass
 class UnknownSubcommand(Exception):
   pass
+class MessageSendFailure(Exception):
+  pass
 
 
 if __name__ == '__main__':
@@ -1432,7 +1556,7 @@ if the property was added, modified or d
     usage()
 
   cmd = sys.argv[1]
-  repos_dir = svn.core.svn_path_canonicalize(sys.argv[2])
+  repos_dir = to_str(svn.core.svn_path_canonicalize(to_bytes(sys.argv[2])))
   try:
     expected_args = cmd_list[cmd]
   except KeyError:
@@ -1455,8 +1579,9 @@ if the property was added, modified or d
   if not os.path.exists(config_fname):
     raise MissingConfig(config_fname)
 
-  svn.core.run_app(main, cmd, config_fname, repos_dir,
-                   sys.argv[3:3+expected_args])
+  ret = svn.core.run_app(main, cmd, config_fname, repos_dir,
+                         sys.argv[3:3+expected_args])
+  sys.exit(1 if ret else 0)
 
 # ------------------------------------------------------------------------
 # TODO

Modified: 
subversion/branches/multi-wc-format/tools/hook-scripts/mailer/tests/mailer-t1.output
URL: 
http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/tools/hook-scripts/mailer/tests/mailer-t1.output?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- 
subversion/branches/multi-wc-format/tools/hook-scripts/mailer/tests/mailer-t1.output
 (original)
+++ 
subversion/branches/multi-wc-format/tools/hook-scripts/mailer/tests/mailer-t1.output
 Fri Jan 14 14:01:45 2022
@@ -1,4 +1,4 @@
-Group: file
+Group: All
 Subject: r1 -  dir1 dir2
 
 Author: mailer test
@@ -9,9 +9,43 @@ Log:
 initial load
 
 Added:
+   dir1/
+   dir1/file3
+   dir1/file4
+   dir2/
+   dir2/file5
+   dir2/file6
    file1
    file2
 
+Added: dir1/file3
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ dir1/file3 Sun Sep  9 01:46:40 2001        (r1)
+@@ -0,0 +1 @@
++file3
+
+Added: dir1/file4
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ dir1/file4 Sun Sep  9 01:46:40 2001        (r1)
+@@ -0,0 +1 @@
++file4
+
+Added: dir2/file5
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ dir2/file5 Sun Sep  9 01:46:40 2001        (r1)
+@@ -0,0 +1 @@
++file5
+
+Added: dir2/file6
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ dir2/file6 Sun Sep  9 01:46:40 2001        (r1)
+@@ -0,0 +1 @@
++file6
+
 Added: file1
 ==============================================================================
 --- /dev/null  00:00:00 1970   (empty, because file is newly added)
@@ -25,7 +59,7 @@ Added: file2
 +++ file2      Sun Sep  9 01:46:40 2001        (r1)
 @@ -0,0 +1 @@
 +file2
-Group: file plus other areas
+Group: file
 Subject: r1 -  dir1 dir2
 
 Author: mailer test
@@ -39,15 +73,6 @@ Added:
    file1
    file2
 
-Changes in other areas also in this revision:
-Added:
-   dir1/
-   dir1/file3
-   dir1/file4
-   dir2/
-   dir2/file5
-   dir2/file6
-
 Added: file1
 ==============================================================================
 --- /dev/null  00:00:00 1970   (empty, because file is newly added)
@@ -61,37 +86,7 @@ Added: file2
 +++ file2      Sun Sep  9 01:46:40 2001        (r1)
 @@ -0,0 +1 @@
 +file2
-
-Diffs of changes in other areas also in this revision:
-
-Added: dir1/file3
-==============================================================================
---- /dev/null  00:00:00 1970   (empty, because file is newly added)
-+++ dir1/file3 Sun Sep  9 01:46:40 2001        (r1)
-@@ -0,0 +1 @@
-+file3
-
-Added: dir1/file4
-==============================================================================
---- /dev/null  00:00:00 1970   (empty, because file is newly added)
-+++ dir1/file4 Sun Sep  9 01:46:40 2001        (r1)
-@@ -0,0 +1 @@
-+file4
-
-Added: dir2/file5
-==============================================================================
---- /dev/null  00:00:00 1970   (empty, because file is newly added)
-+++ dir2/file5 Sun Sep  9 01:46:40 2001        (r1)
-@@ -0,0 +1 @@
-+file5
-
-Added: dir2/file6
-==============================================================================
---- /dev/null  00:00:00 1970   (empty, because file is newly added)
-+++ dir2/file6 Sun Sep  9 01:46:40 2001        (r1)
-@@ -0,0 +1 @@
-+file6
-Group: All
+Group: file plus other areas
 Subject: r1 -  dir1 dir2
 
 Author: mailer test
@@ -102,14 +97,33 @@ Log:
 initial load
 
 Added:
+   file1
+   file2
+
+Changes in other areas also in this revision:
+Added:
    dir1/
    dir1/file3
    dir1/file4
    dir2/
    dir2/file5
    dir2/file6
-   file1
-   file2
+
+Added: file1
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ file1      Sun Sep  9 01:46:40 2001        (r1)
+@@ -0,0 +1 @@
++file1
+
+Added: file2
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ file2      Sun Sep  9 01:46:40 2001        (r1)
+@@ -0,0 +1 @@
++file2
+
+Diffs of changes in other areas also in this revision:
 
 Added: dir1/file3
 ==============================================================================
@@ -138,21 +152,7 @@ Added: dir2/file6
 +++ dir2/file6 Sun Sep  9 01:46:40 2001        (r1)
 @@ -0,0 +1 @@
 +file6
-
-Added: file1
-==============================================================================
---- /dev/null  00:00:00 1970   (empty, because file is newly added)
-+++ file1      Sun Sep  9 01:46:40 2001        (r1)
-@@ -0,0 +1 @@
-+file1
-
-Added: file2
-==============================================================================
---- /dev/null  00:00:00 1970   (empty, because file is newly added)
-+++ file2      Sun Sep  9 01:46:40 2001        (r1)
-@@ -0,0 +1 @@
-+file2
-Group: file
+Group: All
 Subject: r2 -  dir1 dir2
 
 Author: mailer test
@@ -163,9 +163,19 @@ Log:
 two file changes.  Fixes Blah#123
 
 Modified:
+   dir1/   (props changed)
+   dir2/file5
    file1   (props changed)
    file2   (contents, props changed)
 
+Modified: dir2/file5
+==============================================================================
+--- dir2/file5 Sun Sep  9 01:46:40 2001        (r1)
++++ dir2/file5 Sun Sep  9 04:33:20 2001        (r2)
+@@ -1 +1,2 @@
+ file5
++change C2
+
 Modified: file2
 ==============================================================================
 --- file2      Sun Sep  9 01:46:40 2001        (r1)
@@ -204,7 +214,7 @@ Modified: file2
 @@ -1 +1,2 @@
  file2
 +change C1
-Group: All
+Group: file
 Subject: r2 -  dir1 dir2
 
 Author: mailer test
@@ -215,19 +225,9 @@ Log:
 two file changes.  Fixes Blah#123
 
 Modified:
-   dir1/   (props changed)
-   dir2/file5
    file1   (props changed)
    file2   (contents, props changed)
 
-Modified: dir2/file5
-==============================================================================
---- dir2/file5 Sun Sep  9 01:46:40 2001        (r1)
-+++ dir2/file5 Sun Sep  9 04:33:20 2001        (r2)
-@@ -1 +1,2 @@
- file5
-+change C2
-
 Modified: file2
 ==============================================================================
 --- file2      Sun Sep  9 01:46:40 2001        (r1)
@@ -286,6 +286,11 @@ Added:
       - copied unchanged from r2, file1
    dir3/   (props changed)
       - copied from r2, dir1/
+Replaced:
+   dir3/file3
+      - copied unchanged from r1, dir1/file3
+   dir3/file4
+      - copied unchanged from r1, dir1/file4
 
 Copied: dir2/file7 (from r2, file1)
 ==============================================================================
@@ -293,6 +298,20 @@ Copied: dir2/file7 (from r2, file1)
 +++ dir2/file7 Sun Sep  9 07:20:00 2001        (r3, copy of r2, file1)
 @@ -0,0 +1 @@
 +file1
+
+Copied: dir3/file3 (from r1, dir1/file3)
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ dir3/file3 Sun Sep  9 07:20:00 2001        (r3, copy of r1, dir1/file3)
+@@ -0,0 +1 @@
++file3
+
+Copied: dir3/file4 (from r1, dir1/file4)
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ dir3/file4 Sun Sep  9 07:20:00 2001        (r3, copy of r1, dir1/file4)
+@@ -0,0 +1 @@
++file4
 Group: All
 Subject: r4 - dir3
 
@@ -314,7 +333,7 @@ Copied and modified: dir3/file8 (from r2
 @@ -1 +1,2 @@
  file1
 +change C3
-Group: file
+Group: All
 Subject: r5 -  dir1 dir3
 
 Author: mailer test
@@ -325,8 +344,10 @@ Log:
 changes and deletes of properties
 
 Modified:
+   dir1/   (props changed)
+   dir3/   (props changed)
    file2   (props changed)
-Group: file plus other areas
+Group: file
 Subject: r5 -  dir1 dir3
 
 Author: mailer test
@@ -338,12 +359,7 @@ changes and deletes of properties
 
 Modified:
    file2   (props changed)
-
-Changes in other areas also in this revision:
-Modified:
-   dir1/   (props changed)
-   dir3/   (props changed)
-Group: All
+Group: file plus other areas
 Subject: r5 -  dir1 dir3
 
 Author: mailer test
@@ -354,10 +370,13 @@ Log:
 changes and deletes of properties
 
 Modified:
+   file2   (props changed)
+
+Changes in other areas also in this revision:
+Modified:
    dir1/   (props changed)
    dir3/   (props changed)
-   file2   (props changed)
-Group: file
+Group: All
 Subject: r6 -  dir1 dir4
 
 Author: mailer test
@@ -368,7 +387,18 @@ Log:
 mixed addition and change.  Fixes Blaz#456 Blah#987
 
 Added:
+   dir4/
    file9
+Modified:
+   dir1/file3
+
+Modified: dir1/file3
+==============================================================================
+--- dir1/file3 Sun Sep  9 12:53:20 2001        (r5)
++++ dir1/file3 Sun Sep  9 15:40:00 2001        (r6)
+@@ -1 +1,2 @@
+ file3
++change C4
 
 Added: file9
 ==============================================================================
@@ -376,8 +406,8 @@ Added: file9
 +++ file9      Sun Sep  9 15:40:00 2001        (r6)
 @@ -0,0 +1 @@
 +file9
-Group: file plus other areas
-Subject: r6 -  dir1 dir4
+Group: bugtracker
+Subject: Fix for Blah#987: r6 -  dir1 dir4
 
 Author: mailer test
 Date: Sun Sep  9 15:40:00 2001
@@ -387,23 +417,11 @@ Log:
 mixed addition and change.  Fixes Blaz#456 Blah#987
 
 Added:
-   file9
-
-Changes in other areas also in this revision:
-Added:
    dir4/
+   file9
 Modified:
    dir1/file3
 
-Added: file9
-==============================================================================
---- /dev/null  00:00:00 1970   (empty, because file is newly added)
-+++ file9      Sun Sep  9 15:40:00 2001        (r6)
-@@ -0,0 +1 @@
-+file9
-
-Diffs of changes in other areas also in this revision:
-
 Modified: dir1/file3
 ==============================================================================
 --- dir1/file3 Sun Sep  9 12:53:20 2001        (r5)
@@ -411,6 +429,13 @@ Modified: dir1/file3
 @@ -1 +1,2 @@
  file3
 +change C4
+
+Added: file9
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ file9      Sun Sep  9 15:40:00 2001        (r6)
+@@ -0,0 +1 @@
++file9
 Group: bugtracker
 Subject: Fix for Blaz#456: r6 -  dir1 dir4
 
@@ -441,8 +466,8 @@ Added: file9
 +++ file9      Sun Sep  9 15:40:00 2001        (r6)
 @@ -0,0 +1 @@
 +file9
-Group: bugtracker
-Subject: Fix for Blah#987: r6 -  dir1 dir4
+Group: file
+Subject: r6 -  dir1 dir4
 
 Author: mailer test
 Date: Sun Sep  9 15:40:00 2001
@@ -452,18 +477,7 @@ Log:
 mixed addition and change.  Fixes Blaz#456 Blah#987
 
 Added:
-   dir4/
    file9
-Modified:
-   dir1/file3
-
-Modified: dir1/file3
-==============================================================================
---- dir1/file3 Sun Sep  9 12:53:20 2001        (r5)
-+++ dir1/file3 Sun Sep  9 15:40:00 2001        (r6)
-@@ -1 +1,2 @@
- file3
-+change C4
 
 Added: file9
 ==============================================================================
@@ -471,7 +485,7 @@ Added: file9
 +++ file9      Sun Sep  9 15:40:00 2001        (r6)
 @@ -0,0 +1 @@
 +file9
-Group: All
+Group: file plus other areas
 Subject: r6 -  dir1 dir4
 
 Author: mailer test
@@ -482,11 +496,23 @@ Log:
 mixed addition and change.  Fixes Blaz#456 Blah#987
 
 Added:
-   dir4/
    file9
+
+Changes in other areas also in this revision:
+Added:
+   dir4/
 Modified:
    dir1/file3
 
+Added: file9
+==============================================================================
+--- /dev/null  00:00:00 1970   (empty, because file is newly added)
++++ file9      Sun Sep  9 15:40:00 2001        (r6)
+@@ -0,0 +1 @@
++file9
+
+Diffs of changes in other areas also in this revision:
+
 Modified: dir1/file3
 ==============================================================================
 --- dir1/file3 Sun Sep  9 12:53:20 2001        (r5)
@@ -494,13 +520,47 @@ Modified: dir1/file3
 @@ -1 +1,2 @@
  file3
 +change C4
+Group: All
+Subject: r7 -  dir1 dir2 dir3 dir3/dir5
 
-Added: file9
+Author: mailer test
+Date: Sun Sep  9 18:26:40 2001
+New Revision: 7
+
+Log:
+adds, deletes, and a change
+
+Added:
+   dir1/file10
+   dir3/dir5/
+Deleted:
+   dir2/
+   file2
+Modified:
+   dir3/file3
+
+Added: dir1/file10
 ==============================================================================
 --- /dev/null  00:00:00 1970   (empty, because file is newly added)
-+++ file9      Sun Sep  9 15:40:00 2001        (r6)
++++ dir1/file10        Sun Sep  9 18:26:40 2001        (r7)
 @@ -0,0 +1 @@
-+file9
++file10
+
+Modified: dir3/file3
+==============================================================================
+--- dir3/file3 Sun Sep  9 15:40:00 2001        (r6)
++++ dir3/file3 Sun Sep  9 18:26:40 2001        (r7)
+@@ -1 +1,2 @@
+ file3
++change C5
+
+Deleted: file2
+==============================================================================
+--- file2      Sun Sep  9 18:26:40 2001        (r6)
++++ /dev/null  00:00:00 1970   (deleted)
+@@ -1,2 +0,0 @@
+-file2
+-change C1
 Group: file
 Subject: r7 -  dir1 dir2 dir3 dir3/dir5
 
@@ -568,47 +628,6 @@ Modified: dir3/file3
  file3
 +change C5
 Group: All
-Subject: r7 -  dir1 dir2 dir3 dir3/dir5
-
-Author: mailer test
-Date: Sun Sep  9 18:26:40 2001
-New Revision: 7
-
-Log:
-adds, deletes, and a change
-
-Added:
-   dir1/file10
-   dir3/dir5/
-Deleted:
-   dir2/
-   file2
-Modified:
-   dir3/file3
-
-Added: dir1/file10
-==============================================================================
---- /dev/null  00:00:00 1970   (empty, because file is newly added)
-+++ dir1/file10        Sun Sep  9 18:26:40 2001        (r7)
-@@ -0,0 +1 @@
-+file10
-
-Modified: dir3/file3
-==============================================================================
---- dir3/file3 Sun Sep  9 15:40:00 2001        (r6)
-+++ dir3/file3 Sun Sep  9 18:26:40 2001        (r7)
-@@ -1 +1,2 @@
- file3
-+change C5
-
-Deleted: file2
-==============================================================================
---- file2      Sun Sep  9 18:26:40 2001        (r6)
-+++ /dev/null  00:00:00 1970   (deleted)
-@@ -1,2 +0,0 @@
--file2
--change C1
-Group: All
 Subject: r8 - in dir6: . dir5
 
 Author: mailer test
@@ -644,7 +663,7 @@ Modified: dir6/file4
 @@ -1 +1,2 @@
  file4
 +change C6
-Group: file
+Group: All
 Subject: r9 - 
 
 Author: mailer test
@@ -662,7 +681,7 @@ Modified:
 Added: file11
 ==============================================================================
 Binary file. No diff available.
-Group: file plus other areas
+Group: file
 Subject: r9 - 
 
 Author: mailer test
@@ -680,7 +699,7 @@ Modified:
 Added: file11
 ==============================================================================
 Binary file. No diff available.
-Group: All
+Group: file plus other areas
 Subject: r9 - 
 
 Author: mailer test
@@ -698,7 +717,7 @@ Modified:
 Added: file11
 ==============================================================================
 Binary file. No diff available.
-Group: file
+Group: All
 Subject: r10 - 
 
 Author: mailer test
@@ -715,7 +734,7 @@ Modified:
 Modified: file11
 ==============================================================================
 Binary file (source and/or target). No diff available.
-Group: file plus other areas
+Group: file
 Subject: r10 - 
 
 Author: mailer test
@@ -732,7 +751,7 @@ Modified:
 Modified: file11
 ==============================================================================
 Binary file (source and/or target). No diff available.
-Group: All
+Group: file plus other areas
 Subject: r10 - 
 
 Author: mailer test

Modified: 
subversion/branches/multi-wc-format/tools/hook-scripts/mailer/tests/mailer-tweak.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/tools/hook-scripts/mailer/tests/mailer-tweak.py?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- 
subversion/branches/multi-wc-format/tools/hook-scripts/mailer/tests/mailer-tweak.py
 (original)
+++ 
subversion/branches/multi-wc-format/tools/hook-scripts/mailer/tests/mailer-tweak.py
 Fri Jan 14 14:01:45 2022
@@ -50,10 +50,10 @@ def tweak_dates(pool, home='.'):
 
   for i in range(fs.youngest_rev(fsob, pool)):
     # convert secs into microseconds, then a string
-    date = core.svn_time_to_cstring((DATE_BASE+i*DATE_INCR) * 1000000L, pool)
+    date = core.svn_time_to_cstring((DATE_BASE+i*DATE_INCR) * 1000000, pool)
     #print date
     fs.change_rev_prop(fsob, i+1, core.SVN_PROP_REVISION_DATE, date, pool)
-    fs.change_rev_prop(fsob, i+1, core.SVN_PROP_REVISION_AUTHOR, 'mailer 
test', pool)
+    fs.change_rev_prop(fsob, i+1, core.SVN_PROP_REVISION_AUTHOR, b'mailer 
test', pool)
 
 def main():
   if len(sys.argv) != 2:

Modified: subversion/branches/multi-wc-format/tools/hook-scripts/svnperms.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/tools/hook-scripts/svnperms.py?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/tools/hook-scripts/svnperms.py 
(original)
+++ subversion/branches/multi-wc-format/tools/hook-scripts/svnperms.py Fri Jan 
14 14:01:45 2022
@@ -137,7 +137,8 @@ class Permission:
                     try:
                         groupusers.extend(self._group[token[1:]])
                     except KeyError:
-                        raise Error, "group '%s' not found" % token[1:]
+                        raise Error("group '%s' not found" % \
+                                     token[1:])
                 else:
                     groupusers.append(token)
             self._group[option] = groupusers

Modified: 
subversion/branches/multi-wc-format/tools/hook-scripts/validate-files.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/tools/hook-scripts/validate-files.py?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/tools/hook-scripts/validate-files.py 
(original)
+++ subversion/branches/multi-wc-format/tools/hook-scripts/validate-files.py 
Fri Jan 14 14:01:45 2022
@@ -19,7 +19,13 @@
 """Subversion pre-commit hook script that runs user configured commands
 to validate files in the commit and reject the commit if the commands
 exit with a non-zero exit code.  The script expects a validate-files.conf
-file placed in the conf dir under the repo the commit is for."""
+file placed in the conf dir under the repo the commit is for.
+
+Note: As changed file paths $FILE are always represented as a Unicode (Py3)
+      or UTF-8 (Py2) strings, you might need to set apropriate locale and
+      PYTHONIOENCODING environment variable for this script and
+      commands to handle non-ascii path and command outputs, especially
+      you want to use svnlook cat command to inspect file contents."""
 
 import sys
 import os
@@ -30,11 +36,13 @@ import fnmatch
 try:
     # Python >= 3.0
     import configparser
+    ConfigParser = configparser.ConfigParser
 except ImportError:
     # Python < 3.0
     import ConfigParser as configparser
+    ConfigParser = configparser.SafeConfigParser
 
-class Config(configparser.SafeConfigParser):
+class Config(ConfigParser):
     """Superclass of SafeConfigParser with some customizations
     for this script"""
     def optionxform(self, option):
@@ -80,18 +88,26 @@ class Commands:
             line = p.stdout.readline()
             if not line:
                 break
-            line = line.decode().strip()
+            line = line.strip()
             text_mod = line[0:1]
             # Only if the contents of the file changed (by addition or update)
             # directories always end in / in the svnlook changed output
-            if line[-1] != "/" and (text_mod == "A" or text_mod == "U"):
-                changed.append(line[4:])
+            if line[-1:] != b"/" and (text_mod == b"A" or text_mod == b"U"):
+                changed_path = line[4:]
+                if not isinstance(changed_path, str):
+                    # svnlook always uses UTF-8 for internal path
+                    changed_path = changed_path.decode('utf-8')
+                changed.append(changed_path)
 
         # wait on the command to finish so we can get the
         # returncode/stderr output
         data = p.communicate()
         if p.returncode != 0:
-            sys.stderr.write(data[1].decode())
+            err_mesg = data[1]
+            if sys.stderr.encoding:
+                err_mesg =err_mesg.decode(sys.stderr.encoding,
+                                          'backslashreplace')
+            sys.stderr.write(err_mesg)
             sys.exit(2)
 
         return changed
@@ -109,7 +125,11 @@ class Commands:
         cmd_env['FILE'] = fn
         p = subprocess.Popen(cmd, shell=True, env=cmd_env, 
stderr=subprocess.PIPE)
         data = p.communicate()
-        return (p.returncode, data[1].decode())
+        err_mesg = data[1]
+        if sys.stderr.encoding:
+            err_mesg = err_mesg.decode(sys.stderr.encoding,
+                                       'backslashreplace')
+        return (p.returncode, err_mesg)
 
 def main(repo, txn):
     exitcode = 0

Modified: 
subversion/branches/multi-wc-format/tools/server-side/svn-backup-dumps.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/tools/server-side/svn-backup-dumps.py?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/tools/server-side/svn-backup-dumps.py 
(original)
+++ subversion/branches/multi-wc-format/tools/server-side/svn-backup-dumps.py 
Fri Jan 14 14:01:45 2022
@@ -377,38 +377,48 @@ class SvnBackup:
             return self.exec_cmd_unix(cmd, output, printerr)
 
     def exec_cmd_unix(self, cmd, output=None, printerr=False):
+        if printerr:
+            if sys.hexversion >= 0x3000000:
+                sys.stdout.flush()
+                errout = sys.stdout.buffer
+            else:
+                errout = sys.stdout
+        else:
+            errout = PIPE
         try:
-            proc = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=False)
+            proc = Popen(cmd, stdout=PIPE, stderr=errout, shell=False)
         except:
             return (256, "", "Popen failed (%s ...):\n  %s" % (cmd[0],
                     str(sys.exc_info()[1])))
-        stdout = proc.stdout
-        stderr = proc.stderr
-        self.set_nonblock(stdout)
-        self.set_nonblock(stderr)
-        readfds = [ stdout, stderr ]
-        selres = select.select(readfds, [], [])
-        bufout = ""
-        buferr = ""
-        while len(selres[0]) > 0:
-            for fd in selres[0]:
-                buf = fd.read(16384)
-                if len(buf) == 0:
-                    readfds.remove(fd)
-                elif fd == stdout:
-                    if output:
+        if output is None:
+            bufout, buferr = proc.communicate()
+            rc = proc.returncode
+            if buferr is None:
+                buferr = b""
+        else:
+            stdout = proc.stdout
+            self.set_nonblock(stdout)
+            readfds = [ stdout ]
+            if not printerr:
+                stderr = proc.stderr
+                self.set_nonblock(stderr)
+                readfds.append(stderr)
+            selres = select.select(readfds, [], [])
+            bufout = b""
+            buferr = b""
+            while len(selres[0]) > 0:
+                for fd in selres[0]:
+                    buf = fd.read(16384)
+                    if len(buf) == 0:
+                        readfds.remove(fd)
+                    elif fd == stdout:
                         output.write(buf)
                     else:
-                        bufout += buf
-                else:
-                    if printerr:
-                        sys.stdout.write("%s " % buf)
-                    else:
                         buferr += buf
-            if len(readfds) == 0:
-                break
-            selres = select.select(readfds, [], [])
-        rc = proc.wait()
+                if len(readfds) == 0:
+                    break
+                selres = select.select(readfds, [], [])
+            rc = proc.wait()
         if printerr:
             print("")
         return (rc, bufout, buferr)
@@ -420,8 +430,8 @@ class SvnBackup:
             return (256, "", "Popen failed (%s ...):\n  %s" % (cmd[0],
                     str(sys.exc_info()[1])))
         stdout = proc.stdout
-        bufout = ""
-        buferr = ""
+        bufout = b""
+        buferr = b""
         buf = stdout.read(16384)
         while len(buf) > 0:
             if output:

Modified: subversion/branches/multi-wc-format/tools/server-side/svnauthz.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/tools/server-side/svnauthz.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/tools/server-side/svnauthz.c (original)
+++ subversion/branches/multi-wc-format/tools/server-side/svnauthz.c Fri Jan 14 
14:01:45 2022
@@ -31,6 +31,7 @@
 
 #include "private/svn_fspath.h"
 #include "private/svn_cmdline_private.h"
+#include "svn_private_config.h"
 
 
 /*** Option Processing. ***/
@@ -99,6 +100,9 @@ struct svnauthz_opt_state
 /* Libtool command prefix */
 #define SVNAUTHZ_LT_PREFIX "lt-"
 
+/* The prefix for handling errors and warnings. */
+#define SVNAUTHZ_ERR_PREFIX "svnauthz: "
+
 
 /*** Subcommands. */
 
@@ -224,6 +228,18 @@ read_file_contents(svn_stream_t **conten
   return SVN_NO_ERROR;
 }
 
+/* Handles warning emitted by the authz parser. */
+static void
+handle_parser_warning(void *baton,
+                      const svn_error_t *err,
+                      apr_pool_t *scratch_pool)
+{
+  svn_handle_warning2(stderr, err, SVNAUTHZ_ERR_PREFIX);
+  SVN_UNUSED(baton);
+  SVN_UNUSED(scratch_pool);
+}
+
+
 /* Loads the authz config into *AUTHZ from the file at AUTHZ_FILE
    in repository at REPOS_PATH from the transaction TXN_NAME.  If GROUPS_FILE
    is set, the resulting *AUTHZ will be constructed from AUTHZ_FILE with
@@ -256,7 +272,8 @@ get_authz_from_txn(svn_authz_t **authz,
   else
     groups_contents = NULL;
 
-  err = svn_repos_authz_parse(authz, authz_contents, groups_contents, pool);
+  err = svn_repos_authz_parse2(authz, authz_contents, groups_contents,
+                               handle_parser_warning, NULL, pool, pool);
 
   /* Add the filename to the error stack since the parser doesn't have it. */
   if (err != SVN_NO_ERROR)
@@ -283,9 +300,11 @@ get_authz(svn_authz_t **authz, struct sv
                               opt_state->txn, pool);
 
   /* Else */
-  return svn_repos_authz_read3(authz, opt_state->authz_file,
+  return svn_repos_authz_read4(authz, opt_state->authz_file,
                                opt_state->groups_file,
-                               TRUE, NULL, pool, pool);
+                               TRUE, NULL,
+                               handle_parser_warning, NULL,
+                               pool, pool);
 }
 
 static svn_error_t *
@@ -395,7 +414,12 @@ subcommand_accessof(apr_getopt_t *os, vo
 static svn_boolean_t
 use_compat_mode(const char *cmd, apr_pool_t *pool)
 {
-  cmd = svn_dirent_internal_style(cmd, pool);
+  svn_error_t *err = svn_dirent_internal_style_safe(&cmd, NULL, cmd, pool, 
pool);
+  if (err)
+    {
+      svn_error_clear(err);
+      return FALSE;
+    }
   cmd = svn_dirent_basename(cmd, NULL);
 
   /* Skip over the Libtool command prefix if it exists on the command. */
@@ -437,7 +461,9 @@ canonicalize_access_file(const char **ca
                                    access_file);
         }
 
-      *canonicalized_access_file = svn_uri_canonicalize(access_file, pool);
+      SVN_ERR(svn_uri_canonicalize_safe(
+                  canonicalized_access_file, NULL,
+                  access_file, pool, pool));
     }
   else if (within_txn)
     {
@@ -450,8 +476,9 @@ canonicalize_access_file(const char **ca
     {
       /* If it isn't a URL and there's no transaction flag then it's a
        * dirent to the access file on local disk. */
-      *canonicalized_access_file =
-          svn_dirent_internal_style(access_file, pool);
+      SVN_ERR(svn_dirent_internal_style_safe(
+                  canonicalized_access_file, NULL,
+                  access_file, pool, pool));
     }
 
   return SVN_NO_ERROR;
@@ -626,7 +653,9 @@ sub_main(int *exit_code, int argc, const
                                               pool));
           os->ind++;
 
-          opt_state.repos_path = 
svn_dirent_internal_style(opt_state.repos_path, pool);
+          SVN_ERR(svn_dirent_internal_style_safe(&opt_state.repos_path, NULL,
+                                                 opt_state.repos_path,
+                                                 pool, pool));
         }
 
       /* Exactly 1 non-option argument */
@@ -745,7 +774,7 @@ main(int argc, const char *argv[])
     {
       if (exit_code == 0)
         exit_code = EXIT_FAILURE;
-      svn_cmdline_handle_exit_error(err, NULL, "svnauthz: ");
+      svn_cmdline_handle_exit_error(err, NULL, SVNAUTHZ_ERR_PREFIX);
     }
 
   svn_pool_destroy(pool);

Modified: 
subversion/branches/multi-wc-format/tools/server-side/svnpubsub/daemonize.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/tools/server-side/svnpubsub/daemonize.py?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- 
subversion/branches/multi-wc-format/tools/server-side/svnpubsub/daemonize.py 
(original)
+++ 
subversion/branches/multi-wc-format/tools/server-side/svnpubsub/daemonize.py 
Fri Jan 14 14:01:45 2022
@@ -16,8 +16,16 @@
 #
 # ---------------------------------------------------------------------------
 #
-# This software lives at:
-#    http://gstein.googlecode.com/svn/trunk/python/daemonize.py
+# This code is no longer maintained "upstream", so consider this module
+# a "friendly fork" and is canonical for Apache Subversion's purposes.
+#
+# Use of systemd's single-process mechanism and re-launching of a daemon
+# can greatly simplify daemon coding/management. A possibly-svn-relevant
+# example can be found at:
+#   https://github.com/apache/infrastructure-svnauthz
+#
+# Historical locations for this module were found on svn.webdav.org,
+# gstein.googlecode.com, and (most recently) gstein.svn.beanstalkapp.com.
 #
 
 import os
@@ -56,7 +64,7 @@ class Daemon(object):
       # duplicate the exit code
       sys.exit(e.code)
     except (ChildTerminatedAbnormally, ChildForkFailed,
-            DaemonTerminatedAbnormally, DaemonForkFailed), e:
+            DaemonTerminatedAbnormally, DaemonForkFailed) as e:
       sys.stderr.write('ERROR: %s\n' % e)
       sys.exit(1)
     except ChildResumedIncorrectly:

Modified: subversion/branches/multi-wc-format/win-tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/win-tests.py?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/win-tests.py (original)
+++ subversion/branches/multi-wc-format/win-tests.py Fri Jan 14 14:01:45 2022
@@ -772,6 +772,9 @@ class Httpd:
     local_tmp = os.path.join(self.abs_builddir,
                              CMDLINE_TEST_SCRIPT_NATIVE_PATH,
                              'svn-test-work', 'local_tmp')
+    repositories = os.path.join(self.abs_builddir,
+                                CMDLINE_TEST_SCRIPT_NATIVE_PATH,
+                                'svn-test-work', 'repositories')
     return \
       '<Location /authz-test-work/anon>' + '\n' \
       '  DAV               svn' + '\n' \
@@ -787,6 +790,17 @@ class Httpd:
       '  </IfModule>' + '\n' \
       '  SVNPathAuthz ' + self.path_authz_option + '\n' \
       '</Location>' + '\n' \
+      '<Location /authz-test-work/in-repos-authz>' + '\n' \
+      '  DAV               svn' + '\n' \
+      '  SVNParentPath     ' + repositories + '\n' \
+      '  AuthzSVNReposRelativeAccessFile "^/authz"\n' \
+      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
+      '  AuthType          Basic' + '\n' \
+      '  AuthName          "Subversion Repository"' + '\n' \
+      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
+      '  Require           valid-user' + '\n' \
+      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
+      '</Location>' + '\n' \
       '<Location /authz-test-work/mixed>' + '\n' \
       '  DAV               svn' + '\n' \
       '  SVNParentPath     ' + local_tmp + '\n' \
@@ -1263,7 +1277,11 @@ elif test_swig == 'python':
         or isinstance(i, gen_base.TargetSWIGLib)) and i.lang == 'python':
 
       src = os.path.join(abs_objdir, i.filename)
-      copy_changed_file(src, to_dir=swig_py_libsvn)
+      basename = os.path.basename(src)
+      if sys.version_info[:2] >= (3, 5) \
+          and basename.endswith('.pyd') and objdir == 'Debug':
+        basename = basename[:-4] + '_d.pyd'
+      copy_changed_file(src, os.path.join(swig_py_libsvn, basename))
 
   py_src = os.path.join(abs_srcdir, 'subversion', 'bindings', 'swig', 'python')
 
@@ -1285,7 +1303,8 @@ elif test_swig == 'python':
   if 'PYTHONPATH' in os.environ:
     pythonpath += os.pathsep + os.environ['PYTHONPATH']
 
-  python_exe = 'python.exe'
+  python_exe = sys.executable if objdir != 'Debug' else \
+               os.path.join(os.path.dirname(sys.executable), 'python_d.exe')
   old_cwd = os.getcwd()
   try:
     os.environ['PYTHONPATH'] = pythonpath


Reply via email to