Author: futatuki
Date: Mon Jan 18 16:30:27 2021
New Revision: 1885656
URL: http://svn.apache.org/viewvc?rev=1885656&view=rev
Log:
mailer.py: Restore Python 2 support.
* tools/hook-scripts/mailer/mailer.py
():
- Absorb difference of import module name.
- import codecs, to examine equivalence of codecs.
- Don't import locale.
(to_bytes): New function to absorb differnce between Python 2 and Python 3.
Replace occurence of .encode('utf-8') with this whole in this file.
(to_str): New function to absorb difference between Python 2 and Python 3.
Replace occurence of .decode('utf-8') with this whole in this file.
(_stdin): New variable to hold bytes I/O object for stdin
(_stdout): New variable to hold bytes I/O object for stdout
(OutputBase.make_subject):
- Truncate subject by number of bytes, for compatibility before r1884427.
- Truncate subject on character boundary (don't truncate in the middle of
multi-byte sequence of a UTF-8 character).
(StandardOutput.__init__): Use bytes output interface _stdout for
StandardOutput.write_binary.
(StandardOutput.wirte):
- Override this method only if on Python 3 and encoding of stdout is not
'utf-8'
- Use sys.stdout.encoding instead of locale.getpreferredencoding().
(PropChange.generate): Use bytes input interface _stdin.
(Lock.__init__): Use bytes input interface _stdin.
(DiffURLSelections._get_url): Remove extra trailing spaces.
(DiffGenerator.__getitem__): Prepare bytes and str representation of
base_path, 'base_path_bytes' and 'base_path', then use them properly.
Modified:
subversion/trunk/tools/hook-scripts/mailer/mailer.py
Modified: subversion/trunk/tools/hook-scripts/mailer/mailer.py
URL:
http://svn.apache.org/viewvc/subversion/trunk/tools/hook-scripts/mailer/mailer.py?rev=1885656&r1=1885655&r2=1885656&view=diff
==============================================================================
--- subversion/trunk/tools/hook-scripts/mailer/mailer.py (original)
+++ subversion/trunk/tools/hook-scripts/mailer/mailer.py Mon Jan 18 16:30:27
2021
@@ -46,15 +46,21 @@
import os
import sys
-import configparser
-from urllib.parse import quote as _url_quote
+if sys.hexversion >= 0x3000000:
+ PY3 = True
+ import configparser
+ from urllib.parse import quote as _url_quote
+else:
+ PY3 = False
+ import ConfigParser as configparser
+ from urllib import quote as _url_quote
import time
import subprocess
from io import BytesIO
import smtplib
import re
import tempfile
-import locale
+import codecs
# Minimal version of Subversion's bindings required
_MIN_SVN_VERSION = [1, 5, 0]
@@ -73,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
@@ -151,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,7 +213,7 @@ class OutputBase:
def write(self, output):
"""Append the literal text string OUTPUT to the output representation."""
- return self.write_binary(output.encode('utf-8'))
+ return self.write_binary(to_bytes(output))
def run(self, cmd):
"""Override this method, if the default implementation is not sufficient.
@@ -356,7 +392,7 @@ class StandardOutput(OutputBase):
def __init__(self, cfg, repos, prefix_param):
OutputBase.__init__(self, cfg, repos, prefix_param)
- self.write_binary = sys.stdout.buffer.write
+ self.write_binary = _stdout.write
def start(self, group, params):
self.write("Group: " + (group or "defaults") + "\n")
@@ -365,10 +401,11 @@ class StandardOutput(OutputBase):
def finish(self):
pass
- def write(self, output):
- """Write text as *default* encoding string"""
- return self.write_binary(output.encode(locale.getpreferredencoding(),
- 'backslashreplace'))
+ 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):
@@ -431,8 +468,7 @@ class Commit(Messenger):
self.changelist = sorted(editor.get_changes().items())
- log = (repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG)
- or b'').decode('utf-8')
+ 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 = { }
@@ -451,7 +487,7 @@ class Commit(Messenger):
# figure out the changed directories
dirs = { }
for path, change in self.changelist:
- path = path.decode('utf-8')
+ path = to_str(path)
if change.item_kind == svn.core.svn_node_dir:
dirs[path] = None
else:
@@ -542,7 +578,7 @@ class PropChange(Messenger):
elif self.action == 'M':
self.output.write('Property diff:\n')
tempfile1 = tempfile.NamedTemporaryFile()
- tempfile1.write(sys.stdin.buffer.read())
+ tempfile1.write(_stdin.read())
tempfile1.flush()
tempfile2 = tempfile.NamedTemporaryFile()
tempfile2.write(self.repos.get_rev_prop(self.propname))
@@ -604,8 +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.decode('utf-8').rstrip()
- for x in sys.stdin.buffer.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 = { }
@@ -634,7 +669,7 @@ 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].encode('utf-8'),
+ to_bytes(self.dirlist[0]),
self.pool)
def generate(self):
@@ -709,7 +744,7 @@ class DiffURLSelections:
# KeyError exceptions.
params = self.params.copy()
params['path'] = _url_quote(change.path) if change.path else None
- params['base_path'] = (_url_quote(change.base_path)
+ params['base_path'] = (_url_quote(change.base_path)
if change.base_path else None)
params['rev'] = repos_rev
params['base_rev'] = change.base_rev
@@ -764,8 +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 b'').decode('utf-8'),
+ 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),
@@ -873,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'
@@ -884,10 +920,9 @@ 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.decode('utf-8'), self.date,
- change.base_rev)
+ label1 = '%s\t%s\t(r%s)' % (base_path, self.date, change.base_rev)
label2 = '/dev/null\t00:00:00 1970\t(deleted)'
singular = True
@@ -906,14 +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.decode('utf-8'), base_date,
change.base_rev)
- label2 = '%s\t%s\t(r%s)' \
- % (change.path.decode('utf-8'), 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.
@@ -921,12 +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.decode('utf-8'), self.date,
- self.repos.rev, change.base_rev,
- base_path.decode('utf-8'))
+ 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.
@@ -942,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.decode('utf-8'), self.date, self.repos.rev)
+ % (to_str(change.path), self.date, self.repos.rev)
singular = True
elif not change.text_changed:
@@ -962,9 +996,9 @@ class DiffGenerator:
self.repos.root_this, change.path,
self.pool)
label1 = '%s\t%s\t(r%s)' \
- % (base_path.decode('utf-8'), base_date, change.base_rev)
+ % (base_path, base_date, change.base_rev)
label2 = '%s\t%s\t(r%s)' \
- % (change.path.decode('utf-8'), self.date, self.repos.rev)
+ % (to_str(change.path), self.date, self.repos.rev)
singular = False
if diff:
@@ -1154,7 +1188,7 @@ class TextCommitRenderer:
props = ' (props changed)'
else:
props = ''
- w(' %s%s%s\n' % (d.path.decode('utf-8'), is_dir, props))
+ w(' %s%s%s\n' % (to_str(d.path), is_dir, props))
if d.copied:
if is_dir:
text = ''
@@ -1163,7 +1197,7 @@ class TextCommitRenderer:
else:
text = ' unchanged'
w(' - copied%s from r%d, %s%s\n'
- % (text, d.base_rev, d.base_path.decode('utf-8'), 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
@@ -1180,20 +1214,20 @@ class TextCommitRenderer:
w(section_header)
section_header_printed = True
if diff.kind == 'D':
- w('\nDeleted: %s\n' % diff.base_path.decode('utf-8'))
+ w('\nDeleted: %s\n' % to_str(diff.base_path))
elif diff.kind == 'A':
- w('\nAdded: %s\n' % diff.path.decode('utf-8'))
+ w('\nAdded: %s\n' % to_str(diff.path))
elif diff.kind == 'C':
w('\nCopied: %s (from r%d, %s)\n'
- % (diff.path.decode('utf-8'), diff.base_rev,
- diff.base_path.decode('utf-8')))
+ % (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.decode('utf-8'), diff.base_rev,
- diff.base_path.decode('utf-8')))
+ % (to_str(diff.path), diff.base_rev,
+ to_str(diff.base_path)))
else:
# kind == 'M'
- w('\nModified: %s\n' % diff.path.decode('utf-8'))
+ w('\nModified: %s\n' % to_str(diff.path))
if diff.diff_url:
w('URL: %s\n' % diff.diff_url)
@@ -1232,7 +1266,7 @@ class Repository:
self.author = self.get_rev_prop(svn.core.SVN_PROP_REVISION_AUTHOR)
if self.author is not None:
- self.author = self.author.decode('utf-8')
+ self.author = to_str(self.author)
def get_rev_prop(self, propname, rev = None):
if not rev:
@@ -1441,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.decode('utf-8'))
+ match = pattern.match(to_str(path))
if match:
- if exclude_pattern and exclude_pattern.match(path.decode('utf-8')):
+ if exclude_pattern and exclude_pattern.match(to_str(path)):
continue
params = repos_params.copy()
params.update(match.groupdict())
@@ -1522,8 +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].encode('utf-8'))
- repos_dir = repos_dir.decode('utf-8')
+ repos_dir = to_str(svn.core.svn_path_canonicalize(to_bytes(sys.argv[2])))
try:
expected_args = cmd_list[cmd]
except KeyError: