summaryrefslogtreecommitdiff
path: root/tools/hook-scripts
diff options
context:
space:
mode:
authorJames McCoy <jamessan@debian.org>2020-04-10 08:28:37 -0400
committerJames McCoy <jamessan@debian.org>2020-04-10 08:28:37 -0400
commitf04f369300121ce8d8e66eb5aaeb2926e02880e4 (patch)
tree2020cc3a965e09afeae1348ece5300705716728e /tools/hook-scripts
parentff0dcb36b05eb662b51423774a4502bc04378f69 (diff)
New upstream version 1.14.0~rc2
Diffstat (limited to 'tools/hook-scripts')
-rwxr-xr-xtools/hook-scripts/commit-access-control.pl.in2
-rw-r--r--tools/hook-scripts/mailer/mailer.conf.example7
-rwxr-xr-xtools/hook-scripts/mailer/mailer.py192
-rwxr-xr-xtools/hook-scripts/svn2feed.py2
-rwxr-xr-xtools/hook-scripts/svnperms.py2
-rwxr-xr-xtools/hook-scripts/validate-files.py34
6 files changed, 170 insertions, 69 deletions
diff --git a/tools/hook-scripts/commit-access-control.pl.in b/tools/hook-scripts/commit-access-control.pl.in
index ceb5e73..9d772e0 100755
--- a/tools/hook-scripts/commit-access-control.pl.in
+++ b/tools/hook-scripts/commit-access-control.pl.in
@@ -6,7 +6,7 @@
# commit in repository REPOS using the permissions listed in the
# configuration file CONF_FILE.
#
-# $HeadURL: https://svn.apache.org/repos/asf/subversion/branches/1.13.x/tools/hook-scripts/commit-access-control.pl.in $
+# $HeadURL: https://svn.apache.org/repos/asf/subversion/branches/1.14.x/tools/hook-scripts/commit-access-control.pl.in $
# $LastChangedDate: 2009-11-16 19:07:17 +0000 (Mon, 16 Nov 2009) $
# $LastChangedBy: hwright $
# $LastChangedRevision: 880911 $
diff --git a/tools/hook-scripts/mailer/mailer.conf.example b/tools/hook-scripts/mailer/mailer.conf.example
index 3887e6b..1a81c48 100644
--- a/tools/hook-scripts/mailer/mailer.conf.example
+++ b/tools/hook-scripts/mailer/mailer.conf.example
@@ -1,7 +1,7 @@
#
# mailer.conf: example configuration file for mailer.py
#
-# $Id: mailer.conf.example 1777846 2017-01-07 19:35:49Z julianfoad $
+# $Id: mailer.conf.example 1872403 2020-01-07 01:08:27Z futatuki $
[general]
@@ -23,6 +23,11 @@
# This option specifies the hostname for delivery via SMTP.
#smtp_hostname = localhost
+# This option specifies the TCP port number to connect for SMTP.
+# If it is not specified, 25 is used for SMTP and 465 is used for
+# SMTP-Over-SSL by default.
+#smtp_port = 25
+
# Username and password for SMTP servers requiring authorisation.
#smtp_username = example
#smtp_password = example
diff --git a/tools/hook-scripts/mailer/mailer.py b/tools/hook-scripts/mailer/mailer.py
index b3045f3..09a6f75 100755
--- a/tools/hook-scripts/mailer/mailer.py
+++ b/tools/hook-scripts/mailer/mailer.py
@@ -22,10 +22,10 @@
#
# mailer.py: send email describing a commit
#
-# $HeadURL: https://svn.apache.org/repos/asf/subversion/branches/1.13.x/tools/hook-scripts/mailer/mailer.py $
-# $LastChangedDate: 2018-02-18 19:06:38 +0000 (Sun, 18 Feb 2018) $
-# $LastChangedBy: danielsh $
-# $LastChangedRevision: 1824690 $
+# $HeadURL: https://svn.apache.org/repos/asf/subversion/branches/1.14.x/tools/hook-scripts/mailer/mailer.py $
+# $LastChangedDate: 2020-01-07 01:08:27 +0000 (Tue, 07 Jan 2020) $
+# $LastChangedBy: futatuki $
+# $LastChangedRevision: 1872403 $
#
# USAGE: mailer.py commit REPOS REVISION [CONFIG-FILE]
# mailer.py propchange REPOS REVISION AUTHOR REVPROPNAME [CONFIG-FILE]
@@ -123,7 +123,7 @@ def main(pool, cmd, config_fname, repos_dir, cmd_args):
else:
raise UnknownSubcommand(cmd)
- messenger.generate()
+ return messenger.generate()
def remove_leading_slashes(path):
@@ -285,15 +285,73 @@ class SMTPOutput(MailedOutput):
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):
@@ -421,21 +479,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)
+ try:
+ 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)
+ # 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()
+ 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 +519,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(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()
+ except MessageSendFailure:
+ ret = 1
+ return ret
def get_commondir(dirlist):
@@ -564,21 +632,26 @@ class Lock(Messenger):
self.dirlist[0], self.pool)
def generate(self):
+ ret = 0
for (group, param_tuple), (params, paths) in self.groups.items():
- self.output.start(group, params)
+ try:
+ self.output.start(group, params)
- self.output.write('Author: %s\n'
- '%s paths:\n' %
- (self.author, self.do_lock and 'Locked' or 'Unlocked'))
+ 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)
+ 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 ''))
+ if self.do_lock:
+ self.output.write('Comment:\n%s\n' % (self.lock.comment or ''))
- self.output.finish()
+ self.output.finish()
+ except MessageSendFailure:
+ ret = 1
+ return ret
class DiffSelections:
@@ -1394,6 +1467,8 @@ class UnknownMappingSpec(Exception):
pass
class UnknownSubcommand(Exception):
pass
+class MessageSendFailure(Exception):
+ pass
if __name__ == '__main__':
@@ -1455,8 +1530,9 @@ if the property was added, modified or deleted, respectively.
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
diff --git a/tools/hook-scripts/svn2feed.py b/tools/hook-scripts/svn2feed.py
index e2fe2f3..9e21f9c 100755
--- a/tools/hook-scripts/svn2feed.py
+++ b/tools/hook-scripts/svn2feed.py
@@ -70,7 +70,7 @@ Options:
# is actually set only on initial feed creation, and thereafter simply
# re-used from the pickle each time.
-# $HeadURL: https://svn.apache.org/repos/asf/subversion/branches/1.13.x/tools/hook-scripts/svn2feed.py $
+# $HeadURL: https://svn.apache.org/repos/asf/subversion/branches/1.14.x/tools/hook-scripts/svn2feed.py $
# $LastChangedDate: 2016-04-30 08:16:53 +0000 (Sat, 30 Apr 2016) $
# $LastChangedBy: stefan2 $
# $LastChangedRevision: 1741723 $
diff --git a/tools/hook-scripts/svnperms.py b/tools/hook-scripts/svnperms.py
index f029db0..7655ca5 100755
--- a/tools/hook-scripts/svnperms.py
+++ b/tools/hook-scripts/svnperms.py
@@ -21,7 +21,7 @@
#
#
-# $HeadURL: https://svn.apache.org/repos/asf/subversion/branches/1.13.x/tools/hook-scripts/svnperms.py $
+# $HeadURL: https://svn.apache.org/repos/asf/subversion/branches/1.14.x/tools/hook-scripts/svnperms.py $
# $LastChangedDate: 2016-04-30 08:16:53 +0000 (Sat, 30 Apr 2016) $
# $LastChangedBy: stefan2 $
# $LastChangedRevision: 1741723 $
diff --git a/tools/hook-scripts/validate-files.py b/tools/hook-scripts/validate-files.py
index 7169251..7f58ad8 100755
--- a/tools/hook-scripts/validate-files.py
+++ b/tools/hook-scripts/validate-files.py
@@ -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