diff options
author | James McCoy <jamessan@debian.org> | 2020-04-10 08:28:37 -0400 |
---|---|---|
committer | James McCoy <jamessan@debian.org> | 2020-04-10 08:28:37 -0400 |
commit | f04f369300121ce8d8e66eb5aaeb2926e02880e4 (patch) | |
tree | 2020cc3a965e09afeae1348ece5300705716728e /tools/hook-scripts | |
parent | ff0dcb36b05eb662b51423774a4502bc04378f69 (diff) |
New upstream version 1.14.0~rc2
Diffstat (limited to 'tools/hook-scripts')
-rwxr-xr-x | tools/hook-scripts/commit-access-control.pl.in | 2 | ||||
-rw-r--r-- | tools/hook-scripts/mailer/mailer.conf.example | 7 | ||||
-rwxr-xr-x | tools/hook-scripts/mailer/mailer.py | 192 | ||||
-rwxr-xr-x | tools/hook-scripts/svn2feed.py | 2 | ||||
-rwxr-xr-x | tools/hook-scripts/svnperms.py | 2 | ||||
-rwxr-xr-x | tools/hook-scripts/validate-files.py | 34 |
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 |