From cdb9bff29f46b32319861d0728223d08c58847a1 Mon Sep 17 00:00:00 2001 From: Julen Landa Alustiza Date: Jun 25 2019 06:09:33 +0000 Subject: pagure/hooks/files/git_multimail_upstream.py: fix black formatting --- diff --git a/pagure/hooks/files/git_multimail_upstream.py b/pagure/hooks/files/git_multimail_upstream.py index 46926c4..8cdd156 100755 --- a/pagure/hooks/files/git_multimail_upstream.py +++ b/pagure/hooks/files/git_multimail_upstream.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -__version__ = '1.4.0' +__version__ = "1.4.0" # Copyright (c) 2015-2016 Matthieu Moy and others # Copyright (c) 2012-2014 Michael Haggerty and others @@ -60,6 +60,7 @@ import shlex import optparse import logging import smtplib + try: import ssl except ImportError: @@ -71,6 +72,7 @@ import cgi PYTHON3 = sys.version_info >= (3, 0) if sys.version_info <= (2, 5): + def all(iterable): for element in iterable: if not element: @@ -83,13 +85,14 @@ def is_ascii(s): if PYTHON3: + def is_string(s): return isinstance(s, str) def str_to_bytes(s): return s.encode(ENCODING) - def bytes_to_str(s, errors='strict'): + def bytes_to_str(s, errors="strict"): return s.decode(ENCODING, errors) unicode = str @@ -110,7 +113,10 @@ if PYTHON3: return out.decode(sys.getdefaultencoding()) except UnicodeEncodeError: return out.decode(ENCODING) + + else: + def is_string(s): try: return isinstance(s, basestring) @@ -120,7 +126,7 @@ else: def str_to_bytes(s): return s - def bytes_to_str(s, errors='strict'): + def bytes_to_str(s, errors="strict"): return s def write_str(f, msg): @@ -152,35 +158,39 @@ except ImportError: DEBUG = False -ZEROS = '0' * 40 -LOGBEGIN = '- Log -----------------------------------------------------------------\n' -LOGEND = '-----------------------------------------------------------------------\n' +ZEROS = "0" * 40 +LOGBEGIN = ( + "- Log -----------------------------------------------------------------\n" +) +LOGEND = ( + "-----------------------------------------------------------------------\n" +) -ADDR_HEADERS = set(['from', 'to', 'cc', 'bcc', 'reply-to', 'sender']) +ADDR_HEADERS = set(["from", "to", "cc", "bcc", "reply-to", "sender"]) # It is assumed in many places that the encoding is uniformly UTF-8, # so changing these constants is unsupported. But define them here # anyway, to make it easier to find (at least most of) the places # where the encoding is important. -(ENCODING, CHARSET) = ('UTF-8', 'utf-8') +(ENCODING, CHARSET) = ("UTF-8", "utf-8") REF_CREATED_SUBJECT_TEMPLATE = ( - '%(emailprefix)s%(refname_type)s %(short_refname)s created' - ' (now %(newrev_short)s)' - ) + "%(emailprefix)s%(refname_type)s %(short_refname)s created" + " (now %(newrev_short)s)" +) REF_UPDATED_SUBJECT_TEMPLATE = ( - '%(emailprefix)s%(refname_type)s %(short_refname)s updated' - ' (%(oldrev_short)s -> %(newrev_short)s)' - ) + "%(emailprefix)s%(refname_type)s %(short_refname)s updated" + " (%(oldrev_short)s -> %(newrev_short)s)" +) REF_DELETED_SUBJECT_TEMPLATE = ( - '%(emailprefix)s%(refname_type)s %(short_refname)s deleted' - ' (was %(oldrev_short)s)' - ) + "%(emailprefix)s%(refname_type)s %(short_refname)s deleted" + " (was %(oldrev_short)s)" +) COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE = ( - '%(emailprefix)s%(refname_type)s %(short_refname)s updated: %(oneline)s' - ) + "%(emailprefix)s%(refname_type)s %(short_refname)s updated: %(oneline)s" +) REFCHANGE_HEADER_TEMPLATE = """\ Date: %(send_date)s @@ -396,8 +406,8 @@ class CommandError(Exception): self.retcode = retcode Exception.__init__( self, - 'Command "%s" failed with retcode %s' % (' '.join(cmd), retcode,) - ) + 'Command "%s" failed with retcode %s' % (" ".join(cmd), retcode), + ) class ConfigurationException(Exception): @@ -405,7 +415,7 @@ class ConfigurationException(Exception): # The "git" program (this could be changed to include a full path): -GIT_EXECUTABLE = 'git' +GIT_EXECUTABLE = "git" # How "git" should be invoked (including global arguments), as a list @@ -427,9 +437,13 @@ def choose_git_command(): # output of "git --version", though if we needed more # specific version information this would be the place to # do it. - cmd = [GIT_EXECUTABLE, '-c', 'foo.bar=baz', '--version'] + cmd = [GIT_EXECUTABLE, "-c", "foo.bar=baz", "--version"] read_output(cmd) - GIT_CMD = [GIT_EXECUTABLE, '-c', 'i18n.logoutputencoding=%s' % (ENCODING,)] + GIT_CMD = [ + GIT_EXECUTABLE, + "-c", + "i18n.logoutputencoding=%s" % (ENCODING,), + ] except CommandError: GIT_CMD = [GIT_EXECUTABLE] @@ -449,21 +463,24 @@ def read_output(cmd, input=None, keepends=False, **kw): input = str_to_bytes(input) else: stdin = None - errors = 'strict' - if 'errors' in kw: - errors = kw['errors'] - del kw['errors'] + errors = "strict" + if "errors" in kw: + errors = kw["errors"] + del kw["errors"] p = subprocess.Popen( tuple(str_to_bytes(w) for w in cmd), - stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw - ) + stdin=stdin, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kw + ) (out, err) = p.communicate(input) out = bytes_to_str(out, errors=errors) retcode = p.wait() if retcode: raise CommandError(cmd, retcode) if not keepends: - out = out.rstrip('\n\r') + out = out.rstrip("\n\r") return out @@ -493,8 +510,8 @@ def git_rev_list_ish(cmd, spec, args=None, **kw): return [] if args is None: args = [] - args = [cmd, '--stdin'] + args - spec_stdin = ''.join(s + '\n' for s in spec) + args = [cmd, "--stdin"] + args + spec_stdin = "".join(s + "\n" for s in spec) return read_git_lines(args, input=spec_stdin, **kw) @@ -504,7 +521,7 @@ def git_rev_list(spec, **kw): See git_rev_list_ish() for parameter and return value documentation. """ - return git_rev_list_ish('rev-list', spec, **kw) + return git_rev_list_ish("rev-list", spec, **kw) def git_log(spec, **kw): @@ -513,7 +530,7 @@ def git_log(spec, **kw): See git_rev_list_ish() for parameter and return value documentation. """ - return git_rev_list_ish('log', spec, **kw) + return git_rev_list_ish("log", spec, **kw) def header_encode(text, header_name=None): @@ -521,14 +538,16 @@ def header_encode(text, header_name=None): # Convert to unicode, if required. if not isinstance(text, unicode): - text = unicode(text, 'utf-8') + text = unicode(text, "utf-8") if is_ascii(text): - charset = 'ascii' + charset = "ascii" else: - charset = 'utf-8' + charset = "utf-8" - return Header(text, header_name=header_name, charset=Charset(charset)).encode() + return Header( + text, header_name=header_name, charset=Charset(charset) + ).encode() def addr_header_encode(text, header_name=None): @@ -537,19 +556,21 @@ def addr_header_encode(text, header_name=None): # Convert to unicode, if required. if not isinstance(text, unicode): - text = unicode(text, 'utf-8') + text = unicode(text, "utf-8") - text = ', '.join( + text = ", ".join( formataddr((header_encode(name), emailaddr)) for name, emailaddr in getaddresses([text]) - ) + ) if is_ascii(text): - charset = 'ascii' + charset = "ascii" else: - charset = 'utf-8' + charset = "utf-8" - return Header(text, header_name=header_name, charset=Charset(charset)).encode() + return Header( + text, header_name=header_name, charset=Charset(charset) + ).encode() class Config(object): @@ -564,7 +585,7 @@ class Config(object): self.section = section if git_config: self.env = os.environ.copy() - self.env['GIT_CONFIG'] = git_config + self.env["GIT_CONFIG"] = git_config else: self.env = None @@ -572,8 +593,8 @@ class Config(object): def _split(s): """Split NUL-terminated values.""" - words = s.split('\0') - assert words[-1] == '' + words = s.split("\0") + assert words[-1] == "" return words[:-1] @staticmethod @@ -586,24 +607,32 @@ class Config(object): """ if isinstance(c, str): c = (c,) - parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '') + parameters = os.environ.get("GIT_CONFIG_PARAMETERS", "") if parameters: - parameters += ' ' + parameters += " " # git expects GIT_CONFIG_PARAMETERS to be of the form # "'name1=value1' 'name2=value2' 'name3=value3'" # including everything inside the double quotes (but not the double # quotes themselves). Spacing is critical. Also, if a value contains # a literal single quote that quote must be represented using the # four character sequence: '\'' - parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in c) - os.environ['GIT_CONFIG_PARAMETERS'] = parameters + parameters += " ".join("'" + x.replace("'", "'\\''") + "'" for x in c) + os.environ["GIT_CONFIG_PARAMETERS"] = parameters def get(self, name, default=None): try: - values = self._split(read_git_output( - ['config', '--get', '--null', '%s.%s' % (self.section, name)], - env=self.env, keepends=True, - )) + values = self._split( + read_git_output( + [ + "config", + "--get", + "--null", + "%s.%s" % (self.section, name), + ], + env=self.env, + keepends=True, + ) + ) assert len(values) == 1 return values[0] except CommandError: @@ -612,12 +641,12 @@ class Config(object): def get_bool(self, name, default=None): try: value = read_git_output( - ['config', '--get', '--bool', '%s.%s' % (self.section, name)], + ["config", "--get", "--bool", "%s.%s" % (self.section, name)], env=self.env, - ) + ) except CommandError: return default - return value == 'true' + return value == "true" def get_all(self, name, default=None): """Read a (possibly multivalued) setting from the configuration. @@ -626,10 +655,18 @@ class Config(object): is unset.""" try: - return self._split(read_git_output( - ['config', '--get-all', '--null', '%s.%s' % (self.section, name)], - env=self.env, keepends=True, - )) + return self._split( + read_git_output( + [ + "config", + "--get-all", + "--null", + "%s.%s" % (self.section, name), + ], + env=self.env, + keepends=True, + ) + ) except CommandError: t, e, traceback = sys.exc_info() if e.retcode == 1: @@ -641,15 +678,14 @@ class Config(object): def set(self, name, value): read_git_output( - ['config', '%s.%s' % (self.section, name), value], - env=self.env, - ) + ["config", "%s.%s" % (self.section, name), value], env=self.env + ) def add(self, name, value): read_git_output( - ['config', '--add', '%s.%s' % (self.section, name), value], + ["config", "--add", "%s.%s" % (self.section, name), value], env=self.env, - ) + ) def __contains__(self, name): return self.get_all(name, default=None) is not None @@ -662,9 +698,9 @@ class Config(object): def unset_all(self, name): try: read_git_output( - ['config', '--unset-all', '%s.%s' % (self.section, name)], + ["config", "--unset-all", "%s.%s" % (self.section, name)], env=self.env, - ) + ) except CommandError: t, e, traceback = sys.exc_info() if e.retcode == 5: @@ -687,11 +723,9 @@ def generate_summaries(*log_args): commit specified by log_args (subject is the first line of the commit message as a string without EOLs).""" - cmd = [ - 'log', '--abbrev', '--format=%h %s', - ] + list(log_args) + ['--'] + cmd = ["log", "--abbrev", "--format=%h %s"] + list(log_args) + ["--"] for line in read_git_lines(cmd): - yield tuple(line.split(' ', 1)) + yield tuple(line.split(" ", 1)) def limit_lines(lines, max_lines): @@ -700,14 +734,14 @@ def limit_lines(lines, max_lines): yield line if index >= max_lines: - yield '... %d lines suppressed ...\n' % (index + 1 - max_lines,) + yield "... %d lines suppressed ...\n" % (index + 1 - max_lines,) def limit_linelength(lines, max_linelength): for line in lines: # Don't forget that lines always include a trailing newline. if len(line) > max_linelength + 1: - line = line[:max_linelength - 7] + ' [...]\n' + line = line[: max_linelength - 7] + " [...]\n" yield line @@ -737,30 +771,30 @@ class GitObject(object): self.sha1 = self.type = self.commit_sha1 = None else: self.sha1 = sha1 - self.type = type or read_git_output(['cat-file', '-t', self.sha1]) + self.type = type or read_git_output(["cat-file", "-t", self.sha1]) - if self.type == 'commit': + if self.type == "commit": self.commit_sha1 = self.sha1 - elif self.type == 'tag': + elif self.type == "tag": try: self.commit_sha1 = read_git_output( - ['rev-parse', '--verify', '%s^0' % (self.sha1,)] - ) + ["rev-parse", "--verify", "%s^0" % (self.sha1,)] + ) except CommandError: # Cannot deref tag to determine commit_sha1 self.commit_sha1 = None else: self.commit_sha1 = None - self.short = read_git_output(['rev-parse', '--short', sha1]) + self.short = read_git_output(["rev-parse", "--short", sha1]) def get_summary(self): """Return (sha1_short, subject) for this commit.""" if not self.sha1: - raise ValueError('Empty commit has no summary') + raise ValueError("Empty commit has no summary") - return next(iter(generate_summaries('--no-walk', self.sha1))) + return next(iter(generate_summaries("--no-walk", self.sha1))) def __eq__(self, other): return isinstance(other, GitObject) and self.sha1 == other.sha1 @@ -807,15 +841,13 @@ class Change(object): values = self.environment.get_values() fromaddr = self.environment.get_fromaddr(change=self) if fromaddr is not None: - values['fromaddr'] = fromaddr - values['multimail_version'] = get_version() + values["fromaddr"] = fromaddr + values["multimail_version"] = get_version() return values # Aliases usable in template strings. Tuple of pairs (destination, # source). - VALUES_ALIAS = ( - ("id", "newrev"), - ) + VALUES_ALIAS = (("id", "newrev"),) def get_values(self, **extra_values): """Return a dictionary {keyword: expansion} for this Change. @@ -866,13 +898,13 @@ class Change(object): values = self.get_values(**extra_values) if self._contains_html_diff: - self._content_type = 'html' + self._content_type = "html" else: - self._content_type = 'plain' - values['contenttype'] = self._content_type + self._content_type = "plain" + values["contenttype"] = self._content_type for line in template.splitlines(): - (name, value) = line.split(': ', 1) + (name, value) = line.split(": ", 1) try: value = value % values @@ -880,16 +912,15 @@ class Change(object): t, e, traceback = sys.exc_info() if DEBUG: self.environment.log_warning( - 'Warning: unknown variable %r in the following line; line skipped:\n' - ' %s\n' - % (e.args[0], line,) - ) + "Warning: unknown variable %r in the following line; line skipped:\n" + " %s\n" % (e.args[0], line) + ) else: if name.lower() in ADDR_HEADERS: value = addr_header_encode(value, name) else: value = header_encode(value, name) - for splitline in ('%s: %s\n' % (name, value)).splitlines(True): + for splitline in ("%s: %s\n" % (name, value)).splitlines(True): yield splitline def generate_email_header(self): @@ -940,7 +971,7 @@ class Change(object): for line in lines: yield cgi.escape(line) - yield '\n' + yield "\n" else: for line in lines: yield line @@ -959,9 +990,10 @@ class Change(object): for line in self.generate_email_header(**extra_header_values): yield line - yield '\n' - html_escape_val = (self.environment.html_in_intro and - self._contains_html_diff) + yield "\n" + html_escape_val = ( + self.environment.html_in_intro and self._contains_html_diff + ) intro = self.generate_email_intro(html_escape_val) if not self.environment.html_in_intro: intro = self._wrap_for_html(intro) @@ -969,7 +1001,9 @@ class Change(object): yield line if self.environment.commitBrowseURL: - for line in self.generate_browse_link(self.environment.commitBrowseURL): + for line in self.generate_browse_link( + self.environment.commitBrowseURL + ): yield line body = self.generate_email_body(push) @@ -990,50 +1024,51 @@ class Change(object): # parse the diff, i.e. look at how many lines do we have in # the hunk headers instead of blindly highlighting everything # that looks like it might be part of a diff. - bgcolor = '' - fgcolor = '' - if line.startswith('--- a/'): + bgcolor = "" + fgcolor = "" + if line.startswith("--- a/"): diff_started = True - bgcolor = 'e0e0ff' - elif line.startswith('diff ') or line.startswith('index '): + bgcolor = "e0e0ff" + elif line.startswith("diff ") or line.startswith("index "): diff_started = True - fgcolor = '808080' + fgcolor = "808080" elif diff_started: - if line.startswith('+++ '): - bgcolor = 'e0e0ff' - elif line.startswith('@@'): - bgcolor = 'e0e0e0' - elif line.startswith('+'): - bgcolor = 'e0ffe0' - elif line.startswith('-'): - bgcolor = 'ffe0e0' - elif line.startswith('commit '): - fgcolor = '808000' - elif line.startswith(' '): - fgcolor = '404040' + if line.startswith("+++ "): + bgcolor = "e0e0ff" + elif line.startswith("@@"): + bgcolor = "e0e0e0" + elif line.startswith("+"): + bgcolor = "e0ffe0" + elif line.startswith("-"): + bgcolor = "ffe0e0" + elif line.startswith("commit "): + fgcolor = "808000" + elif line.startswith(" "): + fgcolor = "404040" # Chop the trailing LF, we don't want it inside
.
                 line = cgi.escape(line[:-1])
 
                 if bgcolor or fgcolor:
-                    style = 'display:block; white-space:pre;'
+                    style = "display:block; white-space:pre;"
                     if bgcolor:
-                        style += 'background:#' + bgcolor + ';'
+                        style += "background:#" + bgcolor + ";"
                     if fgcolor:
-                        style += 'color:#' + fgcolor + ';'
+                        style += "color:#" + fgcolor + ";"
                     # Use a %s\n" % (style, line)
                 else:
-                    line = line + '\n'
+                    line = line + "\n"
 
             yield line
         if self._contains_html_diff:
-            yield '
' - html_escape_val = (self.environment.html_in_footer and - self._contains_html_diff) + yield "" + html_escape_val = ( + self.environment.html_in_footer and self._contains_html_diff + ) footer = self.generate_email_footer(html_escape_val) if not self.environment.html_in_footer: footer = self._wrap_for_html(footer) @@ -1049,7 +1084,7 @@ class Change(object): class Revision(Change): """A Change consisting of a single git commit.""" - CC_RE = re.compile(r'^\s*C[Cc]:\s*(?P[^#]+@[^\s#]*)\s*(#.*)?$') + CC_RE = re.compile(r"^\s*C[Cc]:\s*(?P[^#]+@[^\s#]*)\s*(#.*)?$") def __init__(self, reference_change, rev, num, tot): Change.__init__(self, reference_change.environment) @@ -1059,24 +1094,31 @@ class Revision(Change): self.refname = self.reference_change.refname self.num = num self.tot = tot - self.author = read_git_output(['log', '--no-walk', '--format=%aN <%aE>', self.rev.sha1]) + self.author = read_git_output( + ["log", "--no-walk", "--format=%aN <%aE>", self.rev.sha1] + ) self.recipients = self.environment.get_revision_recipients(self) - self.cc_recipients = '' + self.cc_recipients = "" if self.environment.get_scancommitforcc(): - self.cc_recipients = ', '.join(to.strip() for to in self._cc_recipients()) + self.cc_recipients = ", ".join( + to.strip() for to in self._cc_recipients() + ) if self.cc_recipients: self.environment.log_msg( - 'Add %s to CC for %s' % (self.cc_recipients, self.rev.sha1)) + "Add %s to CC for %s" % (self.cc_recipients, self.rev.sha1) + ) def _cc_recipients(self): cc_recipients = [] - message = read_git_output(['log', '--no-walk', '--format=%b', self.rev.sha1]) - lines = message.strip().split('\n') + message = read_git_output( + ["log", "--no-walk", "--format=%b", self.rev.sha1] + ) + lines = message.strip().split("\n") for line in lines: m = re.match(self.CC_RE, line) if m: - cc_recipients.append(m.group('to')) + cc_recipients.append(m.group("to")) return cc_recipients @@ -1084,82 +1126,94 @@ class Revision(Change): values = Change._compute_values(self) oneline = read_git_output( - ['log', '--format=%s', '--no-walk', self.rev.sha1] - ) + ["log", "--format=%s", "--no-walk", self.rev.sha1] + ) max_subject_length = self.environment.get_max_subject_length() if max_subject_length > 0 and len(oneline) > max_subject_length: - oneline = oneline[:max_subject_length - 6] + ' [...]' - - values['rev'] = self.rev.sha1 - values['rev_short'] = self.rev.short - values['change_type'] = self.change_type - values['refname'] = self.refname - values['newrev'] = self.rev.sha1 - values['short_refname'] = self.reference_change.short_refname - values['refname_type'] = self.reference_change.refname_type - values['reply_to_msgid'] = self.reference_change.msgid - values['num'] = self.num - values['tot'] = self.tot - values['recipients'] = self.recipients + oneline = oneline[: max_subject_length - 6] + " [...]" + + values["rev"] = self.rev.sha1 + values["rev_short"] = self.rev.short + values["change_type"] = self.change_type + values["refname"] = self.refname + values["newrev"] = self.rev.sha1 + values["short_refname"] = self.reference_change.short_refname + values["refname_type"] = self.reference_change.refname_type + values["reply_to_msgid"] = self.reference_change.msgid + values["num"] = self.num + values["tot"] = self.tot + values["recipients"] = self.recipients if self.cc_recipients: - values['cc_recipients'] = self.cc_recipients - values['oneline'] = oneline - values['author'] = self.author + values["cc_recipients"] = self.cc_recipients + values["oneline"] = oneline + values["author"] = self.author reply_to = self.environment.get_reply_to_commit(self) if reply_to: - values['reply_to'] = reply_to + values["reply_to"] = reply_to return values def generate_email_header(self, **extra_values): for line in self.expand_header_lines( - REVISION_HEADER_TEMPLATE, **extra_values - ): + REVISION_HEADER_TEMPLATE, **extra_values + ): yield line def generate_browse_link(self, base_url): - if '%(' not in base_url: - base_url += '%(id)s' + if "%(" not in base_url: + base_url += "%(id)s" url = "".join(self.expand_lines(base_url)) - if self._content_type == 'html': - for line in self.expand_lines(LINK_HTML_TEMPLATE, - html_escape_val=True, - browse_url=url): + if self._content_type == "html": + for line in self.expand_lines( + LINK_HTML_TEMPLATE, html_escape_val=True, browse_url=url + ): yield line - elif self._content_type == 'plain': - for line in self.expand_lines(LINK_TEXT_TEMPLATE, - html_escape_val=False, - browse_url=url): + elif self._content_type == "plain": + for line in self.expand_lines( + LINK_TEXT_TEMPLATE, html_escape_val=False, browse_url=url + ): yield line else: - raise NotImplementedError("Content-type %s unsupported. Please report it as a bug.") + raise NotImplementedError( + "Content-type %s unsupported. Please report it as a bug." + ) def generate_email_intro(self, html_escape_val=False): - for line in self.expand_lines(REVISION_INTRO_TEMPLATE, - html_escape_val=html_escape_val): + for line in self.expand_lines( + REVISION_INTRO_TEMPLATE, html_escape_val=html_escape_val + ): yield line def generate_email_body(self, push): """Show this revision.""" for line in read_git_lines( - ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1], - keepends=True, - errors='replace'): - if line.startswith('Date: ') and self.environment.date_substitute: - yield self.environment.date_substitute + line[len('Date: '):] + ["log"] + self.environment.commitlogopts + ["-1", self.rev.sha1], + keepends=True, + errors="replace", + ): + if ( + line.startswith("Date: ") + and self.environment.date_substitute + ): + yield self.environment.date_substitute + line[ + len("Date: ") : + ] else: yield line def generate_email_footer(self, html_escape_val): - return self.expand_lines(REVISION_FOOTER_TEMPLATE, - html_escape_val=html_escape_val) + return self.expand_lines( + REVISION_FOOTER_TEMPLATE, html_escape_val=html_escape_val + ) def generate_email(self, push, body_filter=None, extra_header_values={}): self._contains_diff() - return Change.generate_email(self, push, body_filter, extra_header_values) + return Change.generate_email( + self, push, body_filter, extra_header_values + ) def get_specific_fromaddr(self): return self.environment.from_commit @@ -1178,7 +1232,7 @@ class ReferenceChange(Change): create() method, which has the logic to decide which derived class to instantiate.""" - REF_RE = re.compile(r'^refs\/(?P[^\/]+)\/(?P.*)$') + REF_RE = re.compile(r"^refs\/(?P[^\/]+)\/(?P.*)$") @staticmethod def create(environment, oldrev, newrev, refname): @@ -1199,60 +1253,60 @@ class ReferenceChange(Change): # - annotated tag m = ReferenceChange.REF_RE.match(refname) if m: - area = m.group('area') - short_refname = m.group('shortname') + area = m.group("area") + short_refname = m.group("shortname") else: - area = '' + area = "" short_refname = refname - if rev.type == 'tag': + if rev.type == "tag": # Annotated tag: klass = AnnotatedTagChange - elif rev.type == 'commit': - if area == 'tags': + elif rev.type == "commit": + if area == "tags": # Non-annotated tag: klass = NonAnnotatedTagChange - elif area == 'heads': + elif area == "heads": # Branch: klass = BranchChange - elif area == 'remotes': + elif area == "remotes": # Tracking branch: environment.log_warning( - '*** Push-update of tracking branch %r\n' - '*** - incomplete email generated.' - % (refname,) - ) + "*** Push-update of tracking branch %r\n" + "*** - incomplete email generated." % (refname,) + ) klass = OtherReferenceChange else: # Some other reference namespace: environment.log_warning( - '*** Push-update of strange reference %r\n' - '*** - incomplete email generated.' - % (refname,) - ) + "*** Push-update of strange reference %r\n" + "*** - incomplete email generated." % (refname,) + ) klass = OtherReferenceChange else: # Anything else (is there anything else?) environment.log_warning( - '*** Unknown type of update to %r (%s)\n' - '*** - incomplete email generated.' - % (refname, rev.type,) - ) + "*** Unknown type of update to %r (%s)\n" + "*** - incomplete email generated." % (refname, rev.type) + ) klass = OtherReferenceChange return klass( environment, - refname=refname, short_refname=short_refname, - old=old, new=new, rev=rev, - ) + refname=refname, + short_refname=short_refname, + old=old, + new=new, + rev=rev, + ) def __init__(self, environment, refname, short_refname, old, new, rev): Change.__init__(self, environment) self.change_type = { - (False, True): 'create', - (True, True): 'update', - (True, False): 'delete', - }[bool(old), bool(new)] + (False, True): "create", + (True, True): "update", + (True, False): "delete", + }[bool(old), bool(new)] self.refname = refname self.short_refname = short_refname self.old = old @@ -1273,25 +1327,25 @@ class ReferenceChange(Change): def _compute_values(self): values = Change._compute_values(self) - values['change_type'] = self.change_type - values['refname_type'] = self.refname_type - values['refname'] = self.refname - values['short_refname'] = self.short_refname - values['msgid'] = self.msgid - values['recipients'] = self.recipients - values['oldrev'] = str(self.old) - values['oldrev_short'] = self.old.short - values['newrev'] = str(self.new) - values['newrev_short'] = self.new.short + values["change_type"] = self.change_type + values["refname_type"] = self.refname_type + values["refname"] = self.refname + values["short_refname"] = self.short_refname + values["msgid"] = self.msgid + values["recipients"] = self.recipients + values["oldrev"] = str(self.old) + values["oldrev_short"] = self.old.short + values["newrev"] = str(self.new) + values["newrev_short"] = self.new.short if self.old: - values['oldrev_type'] = self.old.type + values["oldrev_type"] = self.old.type if self.new: - values['newrev_type'] = self.new.type + values["newrev_type"] = self.new.type reply_to = self.environment.get_reply_to_refchange(self) if reply_to: - values['reply_to'] = reply_to + values["reply_to"] = reply_to return values @@ -1307,7 +1361,9 @@ class ReferenceChange(Change): return None - def generate_combined_email(self, push, revision, body_filter=None, extra_header_values={}): + def generate_combined_email( + self, push, revision, body_filter=None, extra_header_values={} + ): """Generate an email describing this change AND specified revision. Iterate over the lines (including the header lines) of an @@ -1325,24 +1381,25 @@ class ReferenceChange(Change): def get_subject(self): template = { - 'create': REF_CREATED_SUBJECT_TEMPLATE, - 'update': REF_UPDATED_SUBJECT_TEMPLATE, - 'delete': REF_DELETED_SUBJECT_TEMPLATE, - }[self.change_type] + "create": REF_CREATED_SUBJECT_TEMPLATE, + "update": REF_UPDATED_SUBJECT_TEMPLATE, + "delete": REF_DELETED_SUBJECT_TEMPLATE, + }[self.change_type] return self.expand(template) def generate_email_header(self, **extra_values): - if 'subject' not in extra_values: - extra_values['subject'] = self.get_subject() + if "subject" not in extra_values: + extra_values["subject"] = self.get_subject() for line in self.expand_header_lines( - self.header_template, **extra_values - ): + self.header_template, **extra_values + ): yield line def generate_email_intro(self, html_escape_val=False): - for line in self.expand_lines(self.intro_template, - html_escape_val=html_escape_val): + for line in self.expand_lines( + self.intro_template, html_escape_val=html_escape_val + ): yield line def generate_email_body(self, push): @@ -1352,10 +1409,10 @@ class ReferenceChange(Change): generate_update_summary() / generate_delete_summary().""" change_summary = { - 'create': self.generate_create_summary, - 'delete': self.generate_delete_summary, - 'update': self.generate_update_summary, - }[self.change_type](push) + "create": self.generate_create_summary, + "delete": self.generate_delete_summary, + "update": self.generate_update_summary, + }[self.change_type](push) for line in change_summary: yield line @@ -1363,36 +1420,38 @@ class ReferenceChange(Change): yield line def generate_email_footer(self, html_escape_val): - return self.expand_lines(self.footer_template, - html_escape_val=html_escape_val) + return self.expand_lines( + self.footer_template, html_escape_val=html_escape_val + ) def generate_revision_change_graph(self, push): if self.showgraph: - args = ['--graph'] + self.graphopts - for newold in ('new', 'old'): + args = ["--graph"] + self.graphopts + for newold in ("new", "old"): has_newold = False spec = push.get_commits_spec(newold, self) for line in git_log(spec, args=args, keepends=True): if not has_newold: has_newold = True - yield '\n' - yield 'Graph of %s commits:\n\n' % ( - {'new': 'new', 'old': 'discarded'}[newold],) - yield ' ' + line + yield "\n" + yield "Graph of %s commits:\n\n" % ( + {"new": "new", "old": "discarded"}[newold], + ) + yield " " + line if has_newold: - yield '\n' + yield "\n" def generate_revision_change_log(self, new_commits_list): if self.showlog: - yield '\n' - yield 'Detailed log of new commits:\n\n' + yield "\n" + yield "Detailed log of new commits:\n\n" for line in read_git_lines( - ['log', '--no-walk'] + - self.logopts + - new_commits_list + - ['--'], - keepends=True, - ): + ["log", "--no-walk"] + + self.logopts + + new_commits_list + + ["--"], + keepends=True, + ): yield line def generate_new_revision_summary(self, tot, new_commits_list, push): @@ -1417,19 +1476,22 @@ class ReferenceChange(Change): new_revisions = [ Revision(self, GitObject(sha1), num=i + 1, tot=tot) for (i, sha1) in enumerate(sha1s) - ] + ] if new_revisions: - yield self.expand('This %(refname_type)s includes the following new commits:\n') - yield '\n' + yield self.expand( + "This %(refname_type)s includes the following new commits:\n" + ) + yield "\n" for r in new_revisions: (sha1, subject) = r.rev.get_summary() yield r.expand( - BRIEF_SUMMARY_TEMPLATE, action='new', text=subject, - ) - yield '\n' + BRIEF_SUMMARY_TEMPLATE, action="new", text=subject + ) + yield "\n" for line in self.generate_new_revision_summary( - tot, [r.rev.sha1 for r in new_revisions], push): + tot, [r.rev.sha1 for r in new_revisions], push + ): yield line else: for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE): @@ -1446,17 +1508,22 @@ class ReferenceChange(Change): # have already had notification emails; we want such # revisions in the summary even though we will not send # new notification emails for them. - adds = list(generate_summaries( - '--topo-order', '--reverse', '%s..%s' - % (self.old.commit_sha1, self.new.commit_sha1,) - )) + adds = list( + generate_summaries( + "--topo-order", + "--reverse", + "%s..%s" % (self.old.commit_sha1, self.new.commit_sha1), + ) + ) # List of the revisions that were removed from the branch # by this update. This will be empty except for # non-fast-forward updates. - discards = list(generate_summaries( - '%s..%s' % (self.new.commit_sha1, self.old.commit_sha1,) - )) + discards = list( + generate_summaries( + "%s..%s" % (self.new.commit_sha1, self.old.commit_sha1) + ) + ) if adds: new_commits_list = push.get_new_commits(self) @@ -1472,61 +1539,72 @@ class ReferenceChange(Change): if discards and adds: for (sha1, subject) in discards: if sha1 in discarded_commits: - action = 'discard' + action = "discard" else: - action = 'omit' + action = "omit" yield self.expand( - BRIEF_SUMMARY_TEMPLATE, action=action, - rev_short=sha1, text=subject, - ) + BRIEF_SUMMARY_TEMPLATE, + action=action, + rev_short=sha1, + text=subject, + ) for (sha1, subject) in adds: if sha1 in new_commits: - action = 'new' + action = "new" else: - action = 'add' + action = "add" yield self.expand( - BRIEF_SUMMARY_TEMPLATE, action=action, - rev_short=sha1, text=subject, - ) - yield '\n' + BRIEF_SUMMARY_TEMPLATE, + action=action, + rev_short=sha1, + text=subject, + ) + yield "\n" for line in self.expand_lines(NON_FF_TEMPLATE): yield line elif discards: for (sha1, subject) in discards: if sha1 in discarded_commits: - action = 'discard' + action = "discard" else: - action = 'omit' + action = "omit" yield self.expand( - BRIEF_SUMMARY_TEMPLATE, action=action, - rev_short=sha1, text=subject, - ) - yield '\n' + BRIEF_SUMMARY_TEMPLATE, + action=action, + rev_short=sha1, + text=subject, + ) + yield "\n" for line in self.expand_lines(REWIND_ONLY_TEMPLATE): yield line elif adds: (sha1, subject) = self.old.get_summary() yield self.expand( - BRIEF_SUMMARY_TEMPLATE, action='from', - rev_short=sha1, text=subject, - ) + BRIEF_SUMMARY_TEMPLATE, + action="from", + rev_short=sha1, + text=subject, + ) for (sha1, subject) in adds: if sha1 in new_commits: - action = 'new' + action = "new" else: - action = 'add' + action = "add" yield self.expand( - BRIEF_SUMMARY_TEMPLATE, action=action, - rev_short=sha1, text=subject, - ) + BRIEF_SUMMARY_TEMPLATE, + action=action, + rev_short=sha1, + text=subject, + ) - yield '\n' + yield "\n" if new_commits: for line in self.generate_new_revision_summary( - len(new_commits), new_commits_list, push): + len(new_commits), new_commits_list, push + ): yield line else: for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE): @@ -1541,14 +1619,14 @@ class ReferenceChange(Change): # random revision at this point - the user will be interested # in what this revision changed - including the undoing of # previous revisions in the case of non-fast-forward updates. - yield '\n' - yield 'Summary of changes:\n' + yield "\n" + yield "Summary of changes:\n" for line in read_git_lines( - ['diff-tree'] + - self.diffopts + - ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)], - keepends=True, - ): + ["diff-tree"] + + self.diffopts + + ["%s..%s" % (self.old.commit_sha1, self.new.commit_sha1)], + keepends=True, + ): yield line elif self.old.commit_sha1 and not self.new.commit_sha1: @@ -1560,17 +1638,17 @@ class ReferenceChange(Change): discarded_revisions = [ Revision(self, GitObject(sha1), num=i + 1, tot=tot) for (i, sha1) in enumerate(sha1s) - ] + ] if discarded_revisions: for line in self.expand_lines(DISCARDED_REVISIONS_TEMPLATE): yield line - yield '\n' + yield "\n" for r in discarded_revisions: (sha1, subject) = r.rev.get_summary() yield r.expand( - BRIEF_SUMMARY_TEMPLATE, action='discard', text=subject, - ) + BRIEF_SUMMARY_TEMPLATE, action="discard", text=subject + ) for line in self.generate_revision_change_graph(push): yield line else: @@ -1587,10 +1665,9 @@ class ReferenceChange(Change): # This is a new reference and so oldrev is not valid (sha1, subject) = self.new.get_summary() yield self.expand( - BRIEF_SUMMARY_TEMPLATE, action='at', - rev_short=sha1, text=subject, - ) - yield '\n' + BRIEF_SUMMARY_TEMPLATE, action="at", rev_short=sha1, text=subject + ) + yield "\n" def generate_update_summary(self, push): """Called for the change of a pre-existing branch.""" @@ -1602,24 +1679,27 @@ class ReferenceChange(Change): (sha1, subject) = self.old.get_summary() yield self.expand( - BRIEF_SUMMARY_TEMPLATE, action='was', - rev_short=sha1, text=subject, - ) - yield '\n' + BRIEF_SUMMARY_TEMPLATE, action="was", rev_short=sha1, text=subject + ) + yield "\n" def get_specific_fromaddr(self): return self.environment.from_refchange class BranchChange(ReferenceChange): - refname_type = 'branch' + refname_type = "branch" def __init__(self, environment, refname, short_refname, old, new, rev): ReferenceChange.__init__( - self, environment, - refname=refname, short_refname=short_refname, - old=old, new=new, rev=rev, - ) + self, + environment, + refname=refname, + short_refname=short_refname, + old=old, + new=new, + rev=rev, + ) self.recipients = environment.get_refchange_recipients(self) self._single_revision = None @@ -1646,12 +1726,12 @@ class BranchChange(ReferenceChange): try: # If this change is a reference update that doesn't discard # any commits... - if self.change_type != 'update': + if self.change_type != "update": return None if read_git_lines( - ['merge-base', self.old.sha1, self.new.sha1] - ) != [self.old.sha1]: + ["merge-base", self.old.sha1, self.new.sha1] + ) != [self.old.sha1]: return None # Check if this update introduced exactly one non-merge @@ -1669,11 +1749,13 @@ class BranchChange(ReferenceChange): split_line(line) for line in read_git_lines( [ - 'log', '-3', '--format=%H %P', - '%s..%s' % (self.old.sha1, self.new.sha1), - ] - ) - ] + "log", + "-3", + "--format=%H %P", + "%s..%s" % (self.old.sha1, self.new.sha1), + ] + ) + ] if not new_commits: return None @@ -1691,10 +1773,10 @@ class BranchChange(ReferenceChange): # commit is a non-merge commit, though it may make sense to # combine if it is a merge as well. if not ( - len(new_commits) == 1 and - len(new_commits[0][1]) == 1 and - new_commits[0][0] in known_added_sha1s - ): + len(new_commits) == 1 + and len(new_commits[0][1]) == 1 + and new_commits[0][0] in known_added_sha1s + ): return None # We do not want to combine revision and refchange emails if @@ -1706,7 +1788,9 @@ class BranchChange(ReferenceChange): # We ignored the newest commit if it was just a merge of the one # commit being introduced. But we don't want to ignore that # merge commit it it involved conflict resolutions. Check that. - if merge and merge != read_git_output(['diff-tree', '--cc', merge]): + if merge and merge != read_git_output( + ["diff-tree", "--cc", merge] + ): return None # We can combine the refchange and one new revision emails @@ -1718,12 +1802,16 @@ class BranchChange(ReferenceChange): # don't combine reference/revision emails: return None - def generate_combined_email(self, push, revision, body_filter=None, extra_header_values={}): + def generate_combined_email( + self, push, revision, body_filter=None, extra_header_values={} + ): values = revision.get_values() if extra_header_values: values.update(extra_header_values) - if 'subject' not in extra_header_values: - values['subject'] = self.expand(COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE, **values) + if "subject" not in extra_header_values: + values["subject"] = self.expand( + COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE, **values + ) self._single_revision = revision self._contains_diff() @@ -1737,16 +1825,17 @@ class BranchChange(ReferenceChange): # from the BranchChange object. revision._content_type = self._content_type return revision.generate_browse_link(base_url) + self.generate_browse_link = revision_gen_link for line in self.generate_email(push, body_filter, values): yield line def generate_email_body(self, push): - '''Call the appropriate body generation routine. + """Call the appropriate body generation routine. If this is a combined refchange/revision email, the special logic for handling this combined email comes from this function. For - other cases, we just use the normal handling.''' + other cases, we just use the normal handling.""" # If self._single_revision isn't set; don't override if not self._single_revision: @@ -1757,43 +1846,51 @@ class BranchChange(ReferenceChange): # This is a combined refchange/revision email; we first provide # some info from the refchange portion, and then call the revision # generate_email_body function to handle the revision portion. - adds = list(generate_summaries( - '--topo-order', '--reverse', '%s..%s' - % (self.old.commit_sha1, self.new.commit_sha1,) - )) + adds = list( + generate_summaries( + "--topo-order", + "--reverse", + "%s..%s" % (self.old.commit_sha1, self.new.commit_sha1), + ) + ) - yield self.expand("The following commit(s) were added to %(refname)s by this push:\n") + yield self.expand( + "The following commit(s) were added to %(refname)s by this push:\n" + ) for (sha1, subject) in adds: yield self.expand( - BRIEF_SUMMARY_TEMPLATE, action='new', - rev_short=sha1, text=subject, - ) + BRIEF_SUMMARY_TEMPLATE, + action="new", + rev_short=sha1, + text=subject, + ) yield self._single_revision.rev.short + " is described below\n" - yield '\n' + yield "\n" for line in self._single_revision.generate_email_body(push): yield line class AnnotatedTagChange(ReferenceChange): - refname_type = 'annotated tag' + refname_type = "annotated tag" def __init__(self, environment, refname, short_refname, old, new, rev): ReferenceChange.__init__( - self, environment, - refname=refname, short_refname=short_refname, - old=old, new=new, rev=rev, - ) + self, + environment, + refname=refname, + short_refname=short_refname, + old=old, + new=new, + rev=rev, + ) self.recipients = environment.get_announce_recipients(self) self.show_shortlog = environment.announce_show_shortlog ANNOTATED_TAG_FORMAT = ( - '%(*objectname)\n' - '%(*objecttype)\n' - '%(taggername)\n' - '%(taggerdate)' - ) + "%(*objectname)\n" "%(*objecttype)\n" "%(taggername)\n" "%(taggerdate)" + ) def describe_tag(self, push): """Describe the new value of an annotated tag.""" @@ -1801,63 +1898,81 @@ class AnnotatedTagChange(ReferenceChange): # Use git for-each-ref to pull out the individual fields from # the tag [tagobject, tagtype, tagger, tagged] = read_git_lines( - ['for-each-ref', '--format=%s' % (self.ANNOTATED_TAG_FORMAT,), self.refname], - ) + [ + "for-each-ref", + "--format=%s" % (self.ANNOTATED_TAG_FORMAT,), + self.refname, + ] + ) yield self.expand( - BRIEF_SUMMARY_TEMPLATE, action='tagging', - rev_short=tagobject, text='(%s)' % (tagtype,), - ) - if tagtype == 'commit': + BRIEF_SUMMARY_TEMPLATE, + action="tagging", + rev_short=tagobject, + text="(%s)" % (tagtype,), + ) + if tagtype == "commit": # If the tagged object is a commit, then we assume this is a # release, and so we calculate which tag this tag is # replacing try: - prevtag = read_git_output(['describe', '--abbrev=0', '%s^' % (self.new,)]) + prevtag = read_git_output( + ["describe", "--abbrev=0", "%s^" % (self.new,)] + ) except CommandError: prevtag = None if prevtag: - yield ' replaces %s\n' % (prevtag,) + yield " replaces %s\n" % (prevtag,) else: prevtag = None - yield ' length %s bytes\n' % (read_git_output(['cat-file', '-s', tagobject]),) + yield " length %s bytes\n" % ( + read_git_output(["cat-file", "-s", tagobject]), + ) - yield ' by %s\n' % (tagger,) - yield ' on %s\n' % (tagged,) - yield '\n' + yield " by %s\n" % (tagger,) + yield " on %s\n" % (tagged,) + yield "\n" # Show the content of the tag message; this might contain a # change log or release notes so is worth displaying. yield LOGBEGIN - contents = list(read_git_lines(['cat-file', 'tag', self.new.sha1], keepends=True)) - contents = contents[contents.index('\n') + 1:] - if contents and contents[-1][-1:] != '\n': - contents.append('\n') + contents = list( + read_git_lines(["cat-file", "tag", self.new.sha1], keepends=True) + ) + contents = contents[contents.index("\n") + 1 :] + if contents and contents[-1][-1:] != "\n": + contents.append("\n") for line in contents: yield line - if self.show_shortlog and tagtype == 'commit': + if self.show_shortlog and tagtype == "commit": # Only commit tags make sense to have rev-list operations # performed on them - yield '\n' + yield "\n" if prevtag: # Show changes since the previous release revlist = read_git_output( - ['rev-list', '--pretty=short', '%s..%s' % (prevtag, self.new,)], + [ + "rev-list", + "--pretty=short", + "%s..%s" % (prevtag, self.new), + ], keepends=True, - ) + ) else: # No previous tag, show all the changes since time # began revlist = read_git_output( - ['rev-list', '--pretty=short', '%s' % (self.new,)], + ["rev-list", "--pretty=short", "%s" % (self.new,)], keepends=True, - ) - for line in read_git_lines(['shortlog'], input=revlist, keepends=True): + ) + for line in read_git_lines( + ["shortlog"], input=revlist, keepends=True + ): yield line yield LOGEND - yield '\n' + yield "\n" def generate_create_summary(self, push): """Called for the creation of an annotated tag.""" @@ -1885,19 +2000,23 @@ class AnnotatedTagChange(ReferenceChange): for line in self.expand_lines(TAG_DELETED_TEMPLATE): yield line - yield self.expand(' tag was %(oldrev_short)s\n') - yield '\n' + yield self.expand(" tag was %(oldrev_short)s\n") + yield "\n" class NonAnnotatedTagChange(ReferenceChange): - refname_type = 'tag' + refname_type = "tag" def __init__(self, environment, refname, short_refname, old, new, rev): ReferenceChange.__init__( - self, environment, - refname=refname, short_refname=short_refname, - old=old, new=new, rev=rev, - ) + self, + environment, + refname=refname, + short_refname=short_refname, + old=old, + new=new, + rev=rev, + ) self.recipients = environment.get_refchange_recipients(self) def generate_create_summary(self, push): @@ -1923,17 +2042,21 @@ class NonAnnotatedTagChange(ReferenceChange): class OtherReferenceChange(ReferenceChange): - refname_type = 'reference' + refname_type = "reference" def __init__(self, environment, refname, short_refname, old, new, rev): # We use the full refname as short_refname, because otherwise # the full name of the reference would not be obvious from the # text of the email. ReferenceChange.__init__( - self, environment, - refname=refname, short_refname=refname, - old=old, new=new, rev=rev, - ) + self, + environment, + refname=refname, + short_refname=refname, + old=old, + new=new, + rev=rev, + ) self.recipients = environment.get_refchange_recipients(self) @@ -1961,10 +2084,7 @@ class Mailer(object): class SendMailer(Mailer): """Send emails using 'sendmail -oi -t'.""" - SENDMAIL_CANDIDATES = [ - '/usr/sbin/sendmail', - '/usr/lib/sendmail', - ] + SENDMAIL_CANDIDATES = ["/usr/sbin/sendmail", "/usr/lib/sendmail"] @staticmethod def find_sendmail(): @@ -1973,9 +2093,9 @@ class SendMailer(Mailer): return path else: raise ConfigurationException( - 'No sendmail executable found. ' - 'Try setting multimailhook.sendmailCommand.' - ) + "No sendmail executable found. " + "Try setting multimailhook.sendmailCommand." + ) def __init__(self, environment, command=None, envelopesender=None): """Construct a SendMailer instance. @@ -1988,35 +2108,36 @@ class SendMailer(Mailer): if command: self.command = command[:] else: - self.command = [self.find_sendmail(), '-oi', '-t'] + self.command = [self.find_sendmail(), "-oi", "-t"] if envelopesender: - self.command.extend(['-f', envelopesender]) + self.command.extend(["-f", envelopesender]) def send(self, lines, to_addrs): try: p = subprocess.Popen(self.command, stdin=subprocess.PIPE) except OSError: self.environment.get_logger().error( - '*** Cannot execute command: %s\n' % ' '.join(self.command) + - '*** %s\n' % sys.exc_info()[1] + - '*** Try setting multimailhook.mailer to "smtp"\n' + - '*** to send emails without using the sendmail command.\n' - ) + "*** Cannot execute command: %s\n" % " ".join(self.command) + + "*** %s\n" % sys.exc_info()[1] + + '*** Try setting multimailhook.mailer to "smtp"\n' + + "*** to send emails without using the sendmail command.\n" + ) sys.exit(1) try: lines = (str_to_bytes(line) for line in lines) p.stdin.writelines(lines) except Exception: self.environment.get_logger().error( - '*** Error while generating commit email\n' - '*** - mail sending aborted.\n' - ) - if hasattr(p, 'terminate'): + "*** Error while generating commit email\n" + "*** - mail sending aborted.\n" + ) + if hasattr(p, "terminate"): # subprocess.terminate() is not available in Python 2.4 p.terminate() else: import signal + os.kill(p.pid, signal.SIGTERM) raise else: @@ -2029,25 +2150,30 @@ class SendMailer(Mailer): class SMTPMailer(Mailer): """Send emails using Python's smtplib.""" - def __init__(self, environment, - envelopesender, smtpserver, - smtpservertimeout=10.0, smtpserverdebuglevel=0, - smtpencryption='none', - smtpuser='', smtppass='', - smtpcacerts='' - ): + def __init__( + self, + environment, + envelopesender, + smtpserver, + smtpservertimeout=10.0, + smtpserverdebuglevel=0, + smtpencryption="none", + smtpuser="", + smtppass="", + smtpcacerts="", + ): super(SMTPMailer, self).__init__(environment) if not envelopesender: self.environment.get_logger().error( - 'fatal: git_multimail: cannot use SMTPMailer without a sender address.\n' - 'please set either multimailhook.envelopeSender or user.email\n' - ) + "fatal: git_multimail: cannot use SMTPMailer without a sender address.\n" + "please set either multimailhook.envelopeSender or user.email\n" + ) sys.exit(1) - if smtpencryption == 'ssl' and not (smtpuser and smtppass): + if smtpencryption == "ssl" and not (smtpuser and smtppass): raise ConfigurationException( - 'Cannot use SMTPMailer with security option ssl ' - 'without options username and password.' - ) + "Cannot use SMTPMailer with security option ssl " + "without options username and password." + ) self.envelopesender = envelopesender self.smtpserver = smtpserver self.smtpservertimeout = smtpservertimeout @@ -2057,30 +2183,45 @@ class SMTPMailer(Mailer): self.password = smtppass self.smtpcacerts = smtpcacerts try: + def call(klass, server, timeout): try: return klass(server, timeout=timeout) except TypeError: # Old Python versions do not have timeout= argument. return klass(server) - if self.security == 'none': - self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout) - elif self.security == 'ssl': + + if self.security == "none": + self.smtp = call( + smtplib.SMTP, + self.smtpserver, + timeout=self.smtpservertimeout, + ) + elif self.security == "ssl": if self.smtpcacerts: raise smtplib.SMTPException( "Checking certificate is not supported for ssl, prefer starttls" - ) - self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout) - elif self.security == 'tls': - if 'ssl' not in sys.modules: + ) + self.smtp = call( + smtplib.SMTP_SSL, + self.smtpserver, + timeout=self.smtpservertimeout, + ) + elif self.security == "tls": + if "ssl" not in sys.modules: self.environment.get_logger().error( - '*** Your Python version does not have the ssl library installed\n' - '*** smtpEncryption=tls is not available.\n' - '*** Either upgrade Python to 2.6 or later\n' - ' or use git_multimail.py version 1.2.\n') - if ':' not in self.smtpserver: - self.smtpserver += ':587' # default port for TLS - self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout) + "*** Your Python version does not have the ssl library installed\n" + "*** smtpEncryption=tls is not available.\n" + "*** Either upgrade Python to 2.6 or later\n" + " or use git_multimail.py version 1.2.\n" + ) + if ":" not in self.smtpserver: + self.smtpserver += ":587" # default port for TLS + self.smtp = call( + smtplib.SMTP, + self.smtpserver, + timeout=self.smtpservertimeout, + ) # start: ehlo + starttls # equivalent to # self.smtp.ehlo() @@ -2088,25 +2229,28 @@ class SMTPMailer(Mailer): # with acces to the ssl layer self.smtp.ehlo() if not self.smtp.has_extn("starttls"): - raise smtplib.SMTPException("STARTTLS extension not supported by server") + raise smtplib.SMTPException( + "STARTTLS extension not supported by server" + ) resp, reply = self.smtp.docmd("STARTTLS") if resp != 220: - raise smtplib.SMTPException("Wrong answer to the STARTTLS command") + raise smtplib.SMTPException( + "Wrong answer to the STARTTLS command" + ) if self.smtpcacerts: self.smtp.sock = ssl.wrap_socket( self.smtp.sock, ca_certs=self.smtpcacerts, - cert_reqs=ssl.CERT_REQUIRED - ) + cert_reqs=ssl.CERT_REQUIRED, + ) else: self.smtp.sock = ssl.wrap_socket( - self.smtp.sock, - cert_reqs=ssl.CERT_NONE - ) + self.smtp.sock, cert_reqs=ssl.CERT_NONE + ) self.environment.get_logger().error( - '*** Warning, the server certificat is not verified (smtp) ***\n' - '*** set the option smtpCACerts ***\n' - ) + "*** Warning, the server certificat is not verified (smtp) ***\n" + "*** set the option smtpCACerts ***\n" + ) if not hasattr(self.smtp.sock, "read"): # using httplib.FakeSocket with Python 2.5.x or earlier self.smtp.sock.read = self.smtp.sock.recv @@ -2118,22 +2262,25 @@ class SMTPMailer(Mailer): # end: ehlo + starttls self.smtp.ehlo() else: - sys.stdout.write('*** Error: Control reached an invalid option. ***') + sys.stdout.write( + "*** Error: Control reached an invalid option. ***" + ) sys.exit(1) if self.smtpserverdebuglevel > 0: sys.stdout.write( "*** Setting debug on for SMTP server connection (%s) ***\n" - % self.smtpserverdebuglevel) + % self.smtpserverdebuglevel + ) self.smtp.set_debuglevel(self.smtpserverdebuglevel) except Exception: self.environment.get_logger().error( - '*** Error establishing SMTP connection to %s ***\n' - '*** %s\n' - % (self.smtpserver, sys.exc_info()[1])) + "*** Error establishing SMTP connection to %s ***\n" + "*** %s\n" % (self.smtpserver, sys.exc_info()[1]) + ) sys.exit(1) def __del__(self): - if hasattr(self, 'smtp'): + if hasattr(self, "smtp"): self.smtp.quit() del self.smtp @@ -2141,17 +2288,20 @@ class SMTPMailer(Mailer): try: if self.username or self.password: self.smtp.login(self.username, self.password) - msg = ''.join(lines) + msg = "".join(lines) # turn comma-separated list into Python list if needed. if is_string(to_addrs): - to_addrs = [email for (name, email) in getaddresses([to_addrs])] + to_addrs = [ + email for (name, email) in getaddresses([to_addrs]) + ] self.smtp.sendmail(self.envelopesender, to_addrs, msg) except smtplib.SMTPResponseException: err = sys.exc_info()[1] self.environment.get_logger().error( - '*** Error sending email ***\n' - '*** Error %d: %s\n' - % (err.smtp_code, bytes_to_str(err.smtp_error))) + "*** Error sending email ***\n" + "*** Error %d: %s\n" + % (err.smtp_code, bytes_to_str(err.smtp_error)) + ) try: smtp = self.smtp # delete the field before quit() so that in case of @@ -2160,9 +2310,10 @@ class SMTPMailer(Mailer): smtp.quit() except: self.environment.get_logger().error( - '*** Error closing the SMTP connection ***\n' - '*** Exiting anyway ... ***\n' - '*** %s\n' % sys.exc_info()[1]) + "*** Error closing the SMTP connection ***\n" + "*** Exiting anyway ... ***\n" + "*** %s\n" % sys.exc_info()[1] + ) sys.exit(1) @@ -2171,7 +2322,7 @@ class OutputMailer(Mailer): This is intended for debugging purposes.""" - SEPARATOR = '=' * 75 + '\n' + SEPARATOR = "=" * 75 + "\n" def __init__(self, f): self.f = f @@ -2190,9 +2341,9 @@ def get_git_dir(): from the working directory, using Git's usual rules.""" try: - return read_git_output(['rev-parse', '--git-dir']) + return read_git_output(["rev-parse", "--git-dir"]) except CommandError: - sys.stderr.write('fatal: git_multimail: not in a git directory\n') + sys.stderr.write("fatal: git_multimail: not in a git directory\n") sys.exit(1) @@ -2374,7 +2525,7 @@ class Environment(object): - 1 : show basic debug info """ - REPO_NAME_RE = re.compile(r'^(?P.+?)(?:\.git)$') + REPO_NAME_RE = re.compile(r"^(?P.+?)(?:\.git)$") def __init__(self, osenv=None): self.osenv = osenv or os.environ @@ -2384,28 +2535,28 @@ class Environment(object): self.html_in_footer = False self.commitBrowseURL = None self.maxcommitemails = 500 - self.diffopts = ['--stat', '--summary', '--find-copies-harder'] - self.graphopts = ['--oneline', '--decorate'] + self.diffopts = ["--stat", "--summary", "--find-copies-harder"] + self.graphopts = ["--oneline", "--decorate"] self.logopts = [] self.refchange_showgraph = False self.refchange_showlog = False - self.commitlogopts = ['-C', '--stat', '-p', '--cc'] - self.date_substitute = 'AuthorDate: ' + self.commitlogopts = ["-C", "--stat", "-p", "--cc"] + self.date_substitute = "AuthorDate: " self.quiet = False self.stdout = False self.combine_when_single_commit = True self.logger = None self.COMPUTED_KEYS = [ - 'administrator', - 'charset', - 'emailprefix', - 'pusher', - 'pusher_email', - 'repo_path', - 'repo_shortname', - 'sender', - ] + "administrator", + "charset", + "emailprefix", + "pusher", + "pusher_email", + "repo_path", + "repo_shortname", + "sender", + ] self._values = None @@ -2421,7 +2572,7 @@ class Environment(object): basename = os.path.basename(os.path.abspath(self.get_repo_path())) m = self.REPO_NAME_RE.match(basename) if m: - return m.group('name') + return m.group("name") else: return basename @@ -2432,24 +2583,24 @@ class Environment(object): return None def get_fromaddr(self, change=None): - config = Config('user') - fromname = config.get('name', default='') - fromemail = config.get('email', default='') + config = Config("user") + fromname = config.get("name", default="") + fromemail = config.get("email", default="") if fromemail: return formataddr([fromname, fromemail]) return self.get_sender() def get_administrator(self): - return 'the administrator of this repository' + return "the administrator of this repository" def get_emailprefix(self): - return '' + return "" def get_repo_path(self): - if read_git_output(['rev-parse', '--is-bare-repository']) == 'true': + if read_git_output(["rev-parse", "--is-bare-repository"]) == "true": path = get_git_dir() else: - path = read_git_output(['rev-parse', '--show-toplevel']) + path = read_git_output(["rev-parse", "--show-toplevel"]) return os.path.abspath(path) def get_charset(self): @@ -2466,10 +2617,10 @@ class Environment(object): The return value is always a new dictionary.""" if self._values is None: - values = {'': ''} # %()s expands to the empty string. + values = {"": ""} # %()s expands to the empty string. for key in self.COMPUTED_KEYS: - value = getattr(self, 'get_%s' % (key,))() + value = getattr(self, "get_%s" % (key,))() if value is not None: values[key] = value @@ -2584,158 +2735,158 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin): if value is not None and value.lower() == forbidden: raise ConfigurationException( '"%s" is not an allowed setting for %s' % (value, name) - ) + ) def __init__(self, config, **kw): super(ConfigOptionsEnvironmentMixin, self).__init__( config=config, **kw - ) + ) for var, cfg in ( - ('announce_show_shortlog', 'announceshortlog'), - ('refchange_showgraph', 'refchangeShowGraph'), - ('refchange_showlog', 'refchangeshowlog'), - ('quiet', 'quiet'), - ('stdout', 'stdout'), - ): + ("announce_show_shortlog", "announceshortlog"), + ("refchange_showgraph", "refchangeShowGraph"), + ("refchange_showlog", "refchangeshowlog"), + ("quiet", "quiet"), + ("stdout", "stdout"), + ): val = config.get_bool(cfg) if val is not None: setattr(self, var, val) - commit_email_format = config.get('commitEmailFormat') + commit_email_format = config.get("commitEmailFormat") if commit_email_format is not None: if commit_email_format != "html" and commit_email_format != "text": self.log_warning( - '*** Unknown value for multimailhook.commitEmailFormat: %s\n' % - commit_email_format + - '*** Expected either "text" or "html". Ignoring.\n' - ) + "*** Unknown value for multimailhook.commitEmailFormat: %s\n" + % commit_email_format + + '*** Expected either "text" or "html". Ignoring.\n' + ) else: self.commit_email_format = commit_email_format - html_in_intro = config.get_bool('htmlInIntro') + html_in_intro = config.get_bool("htmlInIntro") if html_in_intro is not None: self.html_in_intro = html_in_intro - html_in_footer = config.get_bool('htmlInFooter') + html_in_footer = config.get_bool("htmlInFooter") if html_in_footer is not None: self.html_in_footer = html_in_footer - self.commitBrowseURL = config.get('commitBrowseURL') + self.commitBrowseURL = config.get("commitBrowseURL") - maxcommitemails = config.get('maxcommitemails') + maxcommitemails = config.get("maxcommitemails") if maxcommitemails is not None: try: self.maxcommitemails = int(maxcommitemails) except ValueError: self.log_warning( - '*** Malformed value for multimailhook.maxCommitEmails: %s\n' - % maxcommitemails + - '*** Expected a number. Ignoring.\n' - ) + "*** Malformed value for multimailhook.maxCommitEmails: %s\n" + % maxcommitemails + + "*** Expected a number. Ignoring.\n" + ) - diffopts = config.get('diffopts') + diffopts = config.get("diffopts") if diffopts is not None: self.diffopts = shlex.split(diffopts) - graphopts = config.get('graphOpts') + graphopts = config.get("graphOpts") if graphopts is not None: self.graphopts = shlex.split(graphopts) - logopts = config.get('logopts') + logopts = config.get("logopts") if logopts is not None: self.logopts = shlex.split(logopts) - commitlogopts = config.get('commitlogopts') + commitlogopts = config.get("commitlogopts") if commitlogopts is not None: self.commitlogopts = shlex.split(commitlogopts) - date_substitute = config.get('dateSubstitute') - if date_substitute == 'none': + date_substitute = config.get("dateSubstitute") + if date_substitute == "none": self.date_substitute = None elif date_substitute is not None: self.date_substitute = date_substitute - reply_to = config.get('replyTo') - self.__reply_to_refchange = config.get('replyToRefchange', default=reply_to) - self.forbid_field_values('replyToRefchange', - self.__reply_to_refchange, - ['author']) - self.__reply_to_commit = config.get('replyToCommit', default=reply_to) - - self.from_refchange = config.get('fromRefchange') - self.forbid_field_values('fromRefchange', - self.from_refchange, - ['author', 'none']) - self.from_commit = config.get('fromCommit') - self.forbid_field_values('fromCommit', - self.from_commit, - ['none']) - - combine = config.get_bool('combineWhenSingleCommit') + reply_to = config.get("replyTo") + self.__reply_to_refchange = config.get( + "replyToRefchange", default=reply_to + ) + self.forbid_field_values( + "replyToRefchange", self.__reply_to_refchange, ["author"] + ) + self.__reply_to_commit = config.get("replyToCommit", default=reply_to) + + self.from_refchange = config.get("fromRefchange") + self.forbid_field_values( + "fromRefchange", self.from_refchange, ["author", "none"] + ) + self.from_commit = config.get("fromCommit") + self.forbid_field_values("fromCommit", self.from_commit, ["none"]) + + combine = config.get_bool("combineWhenSingleCommit") if combine is not None: self.combine_when_single_commit = combine - self.log_file = config.get('logFile', default=None) - self.error_log_file = config.get('errorLogFile', default=None) - self.debug_log_file = config.get('debugLogFile', default=None) - if config.get_bool('Verbose', default=False): + self.log_file = config.get("logFile", default=None) + self.error_log_file = config.get("errorLogFile", default=None) + self.debug_log_file = config.get("debugLogFile", default=None) + if config.get_bool("Verbose", default=False): self.verbose = 1 else: self.verbose = 0 def get_administrator(self): return ( - self.config.get('administrator') or - self.get_sender() or - super(ConfigOptionsEnvironmentMixin, self).get_administrator() - ) + self.config.get("administrator") + or self.get_sender() + or super(ConfigOptionsEnvironmentMixin, self).get_administrator() + ) def get_repo_shortname(self): return ( - self.config.get('reponame') or - super(ConfigOptionsEnvironmentMixin, self).get_repo_shortname() - ) + self.config.get("reponame") + or super(ConfigOptionsEnvironmentMixin, self).get_repo_shortname() + ) def get_emailprefix(self): - emailprefix = self.config.get('emailprefix') + emailprefix = self.config.get("emailprefix") if emailprefix is not None: emailprefix = emailprefix.strip() if emailprefix: - emailprefix += ' ' + emailprefix += " " else: - emailprefix = '[%(repo_shortname)s] ' + emailprefix = "[%(repo_shortname)s] " short_name = self.get_repo_shortname() try: - return emailprefix % {'repo_shortname': short_name} + return emailprefix % {"repo_shortname": short_name} except: self.get_logger().error( - '*** Invalid multimailhook.emailPrefix: %s\n' % emailprefix + - '*** %s\n' % sys.exc_info()[1] + - "*** Only the '%(repo_shortname)s' placeholder is allowed\n" - ) + "*** Invalid multimailhook.emailPrefix: %s\n" % emailprefix + + "*** %s\n" % sys.exc_info()[1] + + "*** Only the '%(repo_shortname)s' placeholder is allowed\n" + ) raise ConfigurationException( '"%s" is not an allowed setting for emailPrefix' % emailprefix - ) + ) def get_sender(self): - return self.config.get('envelopesender') + return self.config.get("envelopesender") def process_addr(self, addr, change): - if addr.lower() == 'author': - if hasattr(change, 'author'): + if addr.lower() == "author": + if hasattr(change, "author"): return change.author else: return None - elif addr.lower() == 'pusher': + elif addr.lower() == "pusher": return self.get_pusher_email() - elif addr.lower() == 'none': + elif addr.lower() == "none": return None else: return addr def get_fromaddr(self, change=None): - fromaddr = self.config.get('from') + fromaddr = self.config.get("from") if change: specific_fromaddr = change.get_specific_fromaddr() if specific_fromaddr: @@ -2748,18 +2899,22 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin): def get_reply_to_refchange(self, refchange): if self.__reply_to_refchange is None: - return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_refchange(refchange) + return super( + ConfigOptionsEnvironmentMixin, self + ).get_reply_to_refchange(refchange) else: return self.process_addr(self.__reply_to_refchange, refchange) def get_reply_to_commit(self, revision): if self.__reply_to_commit is None: - return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_commit(revision) + return super( + ConfigOptionsEnvironmentMixin, self + ).get_reply_to_commit(revision) else: return self.process_addr(self.__reply_to_commit, revision) def get_scancommitforcc(self): - return self.config.get('scancommitforcc') + return self.config.get("scancommitforcc") class FilterLinesEnvironmentMixin(Environment): @@ -2780,9 +2935,13 @@ class FilterLinesEnvironmentMixin(Environment): """ - def __init__(self, strict_utf8=True, - email_max_line_length=500, max_subject_length=500, - **kw): + def __init__( + self, + strict_utf8=True, + email_max_line_length=500, + max_subject_length=500, + **kw + ): super(FilterLinesEnvironmentMixin, self).__init__(**kw) self.__strict_utf8 = strict_utf8 self.__email_max_line_length = email_max_line_length @@ -2792,13 +2951,13 @@ class FilterLinesEnvironmentMixin(Environment): lines = super(FilterLinesEnvironmentMixin, self).filter_body(lines) if self.__strict_utf8: if not PYTHON3: - lines = (line.decode(ENCODING, 'replace') for line in lines) + lines = (line.decode(ENCODING, "replace") for line in lines) # Limit the line length in Unicode-space to avoid # splitting characters: if self.__email_max_line_length > 0: lines = limit_linelength(lines, self.__email_max_line_length) if not PYTHON3: - lines = (line.encode(ENCODING, 'replace') for line in lines) + lines = (line.encode(ENCODING, "replace") for line in lines) elif self.__email_max_line_length: lines = limit_linelength(lines, self.__email_max_line_length) @@ -2809,27 +2968,28 @@ class FilterLinesEnvironmentMixin(Environment): class ConfigFilterLinesEnvironmentMixin( - ConfigEnvironmentMixin, - FilterLinesEnvironmentMixin, - ): + ConfigEnvironmentMixin, FilterLinesEnvironmentMixin +): """Handle encoding and maximum line length based on config.""" def __init__(self, config, **kw): - strict_utf8 = config.get_bool('emailstrictutf8', default=None) + strict_utf8 = config.get_bool("emailstrictutf8", default=None) if strict_utf8 is not None: - kw['strict_utf8'] = strict_utf8 + kw["strict_utf8"] = strict_utf8 - email_max_line_length = config.get('emailmaxlinelength') + email_max_line_length = config.get("emailmaxlinelength") if email_max_line_length is not None: - kw['email_max_line_length'] = int(email_max_line_length) + kw["email_max_line_length"] = int(email_max_line_length) - max_subject_length = config.get('subjectMaxLength', default=email_max_line_length) + max_subject_length = config.get( + "subjectMaxLength", default=email_max_line_length + ) if max_subject_length is not None: - kw['max_subject_length'] = int(max_subject_length) + kw["max_subject_length"] = int(max_subject_length) super(ConfigFilterLinesEnvironmentMixin, self).__init__( config=config, **kw - ) + ) class MaxlinesEnvironmentMixin(Environment): @@ -2847,18 +3007,15 @@ class MaxlinesEnvironmentMixin(Environment): class ConfigMaxlinesEnvironmentMixin( - ConfigEnvironmentMixin, - MaxlinesEnvironmentMixin, - ): + ConfigEnvironmentMixin, MaxlinesEnvironmentMixin +): """Limit the email body to the number of lines specified in config.""" def __init__(self, config, **kw): - emailmaxlines = int(config.get('emailmaxlines', default='0')) + emailmaxlines = int(config.get("emailmaxlines", default="0")) super(ConfigMaxlinesEnvironmentMixin, self).__init__( - config=config, - emailmaxlines=emailmaxlines, - **kw - ) + config=config, emailmaxlines=emailmaxlines, **kw + ) class FQDNEnvironmentMixin(Environment): @@ -2866,7 +3023,7 @@ class FQDNEnvironmentMixin(Environment): def __init__(self, fqdn, **kw): super(FQDNEnvironmentMixin, self).__init__(**kw) - self.COMPUTED_KEYS += ['fqdn'] + self.COMPUTED_KEYS += ["fqdn"] self.__fqdn = fqdn def get_fqdn(self): @@ -2877,19 +3034,14 @@ class FQDNEnvironmentMixin(Environment): return self.__fqdn -class ConfigFQDNEnvironmentMixin( - ConfigEnvironmentMixin, - FQDNEnvironmentMixin, - ): +class ConfigFQDNEnvironmentMixin(ConfigEnvironmentMixin, FQDNEnvironmentMixin): """Read the FQDN from the config.""" def __init__(self, config, **kw): - fqdn = config.get('fqdn') + fqdn = config.get("fqdn") super(ConfigFQDNEnvironmentMixin, self).__init__( - config=config, - fqdn=fqdn, - **kw - ) + config=config, fqdn=fqdn, **kw + ) class ComputeFQDNEnvironmentMixin(FQDNEnvironmentMixin): @@ -2897,9 +3049,8 @@ class ComputeFQDNEnvironmentMixin(FQDNEnvironmentMixin): def __init__(self, **kw): super(ComputeFQDNEnvironmentMixin, self).__init__( - fqdn=socket.getfqdn(), - **kw - ) + fqdn=socket.getfqdn(), **kw + ) class PusherDomainEnvironmentMixin(ConfigEnvironmentMixin): @@ -2907,12 +3058,12 @@ class PusherDomainEnvironmentMixin(ConfigEnvironmentMixin): def __init__(self, **kw): super(PusherDomainEnvironmentMixin, self).__init__(**kw) - self.__emaildomain = self.config.get('emaildomain') + self.__emaildomain = self.config.get("emaildomain") def get_pusher_email(self): if self.__emaildomain: # Derive the pusher's full email address in the default way: - return '%s@%s' % (self.get_pusher(), self.__emaildomain) + return "%s@%s" % (self.get_pusher(), self.__emaildomain) else: return super(PusherDomainEnvironmentMixin, self).get_pusher_email() @@ -2921,10 +3072,13 @@ class StaticRecipientsEnvironmentMixin(Environment): """Set recipients statically based on constructor parameters.""" def __init__( - self, - refchange_recipients, announce_recipients, revision_recipients, scancommitforcc, - **kw - ): + self, + refchange_recipients, + announce_recipients, + revision_recipients, + scancommitforcc, + **kw + ): super(StaticRecipientsEnvironmentMixin, self).__init__(**kw) # The recipients for various types of notification emails, as @@ -2939,29 +3093,34 @@ class StaticRecipientsEnvironmentMixin(Environment): self.__revision_recipients = revision_recipients def check(self): - if not (self.get_refchange_recipients(None) or - self.get_announce_recipients(None) or - self.get_revision_recipients(None) or - self.get_scancommitforcc()): - raise ConfigurationException('No email recipients configured!') + if not ( + self.get_refchange_recipients(None) + or self.get_announce_recipients(None) + or self.get_revision_recipients(None) + or self.get_scancommitforcc() + ): + raise ConfigurationException("No email recipients configured!") super(StaticRecipientsEnvironmentMixin, self).check() def get_refchange_recipients(self, refchange): if self.__refchange_recipients is None: - return super(StaticRecipientsEnvironmentMixin, - self).get_refchange_recipients(refchange) + return super( + StaticRecipientsEnvironmentMixin, self + ).get_refchange_recipients(refchange) return self.__refchange_recipients def get_announce_recipients(self, annotated_tag_change): if self.__announce_recipients is None: - return super(StaticRecipientsEnvironmentMixin, - self).get_refchange_recipients(annotated_tag_change) + return super( + StaticRecipientsEnvironmentMixin, self + ).get_refchange_recipients(annotated_tag_change) return self.__announce_recipients def get_revision_recipients(self, revision): if self.__revision_recipients is None: - return super(StaticRecipientsEnvironmentMixin, - self).get_refchange_recipients(revision) + return super( + StaticRecipientsEnvironmentMixin, self + ).get_refchange_recipients(revision) return self.__revision_recipients @@ -2975,44 +3134,46 @@ class CLIRecipientsEnvironmentMixin(Environment): def get_refchange_recipients(self, refchange): if self.__cli_recipients is None: - return super(CLIRecipientsEnvironmentMixin, - self).get_refchange_recipients(refchange) + return super( + CLIRecipientsEnvironmentMixin, self + ).get_refchange_recipients(refchange) return self.__cli_recipients def get_announce_recipients(self, annotated_tag_change): if self.__cli_recipients is None: - return super(CLIRecipientsEnvironmentMixin, - self).get_announce_recipients(annotated_tag_change) + return super( + CLIRecipientsEnvironmentMixin, self + ).get_announce_recipients(annotated_tag_change) return self.__cli_recipients def get_revision_recipients(self, revision): if self.__cli_recipients is None: - return super(CLIRecipientsEnvironmentMixin, - self).get_revision_recipients(revision) + return super( + CLIRecipientsEnvironmentMixin, self + ).get_revision_recipients(revision) return self.__cli_recipients class ConfigRecipientsEnvironmentMixin( - ConfigEnvironmentMixin, - StaticRecipientsEnvironmentMixin - ): + ConfigEnvironmentMixin, StaticRecipientsEnvironmentMixin +): """Determine recipients statically based on config.""" def __init__(self, config, **kw): super(ConfigRecipientsEnvironmentMixin, self).__init__( config=config, refchange_recipients=self._get_recipients( - config, 'refchangelist', 'mailinglist', - ), + config, "refchangelist", "mailinglist" + ), announce_recipients=self._get_recipients( - config, 'announcelist', 'refchangelist', 'mailinglist', - ), + config, "announcelist", "refchangelist", "mailinglist" + ), revision_recipients=self._get_recipients( - config, 'commitlist', 'mailinglist', - ), - scancommitforcc=config.get('scancommitforcc'), + config, "commitlist", "mailinglist" + ), + scancommitforcc=config.get("scancommitforcc"), **kw - ) + ) def _get_recipients(self, config, *names): """Return the recipients for a particular type of message. @@ -3030,55 +3191,65 @@ class ConfigRecipientsEnvironmentMixin( if lines is not None: lines = [line.strip() for line in lines] # Single "none" is a special value equivalen to empty string. - if lines == ['none']: - lines = [''] - return ', '.join(lines) + if lines == ["none"]: + lines = [""] + return ", ".join(lines) else: - return '' + return "" class StaticRefFilterEnvironmentMixin(Environment): """Set branch filter statically based on constructor parameters.""" - def __init__(self, ref_filter_incl_regex, ref_filter_excl_regex, - ref_filter_do_send_regex, ref_filter_dont_send_regex, - **kw): + def __init__( + self, + ref_filter_incl_regex, + ref_filter_excl_regex, + ref_filter_do_send_regex, + ref_filter_dont_send_regex, + **kw + ): super(StaticRefFilterEnvironmentMixin, self).__init__(**kw) if ref_filter_incl_regex and ref_filter_excl_regex: raise ConfigurationException( - "Cannot specify both a ref inclusion and exclusion regex.") + "Cannot specify both a ref inclusion and exclusion regex." + ) self.__is_inclusion_filter = bool(ref_filter_incl_regex) default_exclude = self.get_default_ref_ignore_regex() if ref_filter_incl_regex: ref_filter_regex = ref_filter_incl_regex elif ref_filter_excl_regex: - ref_filter_regex = ref_filter_excl_regex + '|' + default_exclude + ref_filter_regex = ref_filter_excl_regex + "|" + default_exclude else: ref_filter_regex = default_exclude try: self.__compiled_regex = re.compile(ref_filter_regex) except Exception: raise ConfigurationException( - 'Invalid Ref Filter Regex "%s": %s' % (ref_filter_regex, sys.exc_info()[1])) + 'Invalid Ref Filter Regex "%s": %s' + % (ref_filter_regex, sys.exc_info()[1]) + ) if ref_filter_do_send_regex and ref_filter_dont_send_regex: raise ConfigurationException( - "Cannot specify both a ref doSend and dontSend regex.") + "Cannot specify both a ref doSend and dontSend regex." + ) self.__is_do_send_filter = bool(ref_filter_do_send_regex) if ref_filter_do_send_regex: ref_filter_send_regex = ref_filter_do_send_regex elif ref_filter_dont_send_regex: ref_filter_send_regex = ref_filter_dont_send_regex else: - ref_filter_send_regex = '.*' + ref_filter_send_regex = ".*" self.__is_do_send_filter = True try: self.__send_compiled_regex = re.compile(ref_filter_send_regex) except Exception: raise ConfigurationException( - 'Invalid Ref Filter Regex "%s": %s' % - (ref_filter_send_regex, sys.exc_info()[1])) + 'Invalid Ref Filter Regex "%s": %s' + % (ref_filter_send_regex, sys.exc_info()[1]) + ) def get_ref_filter_regex(self, send_filter=False): if send_filter: @@ -3088,9 +3259,8 @@ class StaticRefFilterEnvironmentMixin(Environment): class ConfigRefFilterEnvironmentMixin( - ConfigEnvironmentMixin, - StaticRefFilterEnvironmentMixin - ): + ConfigEnvironmentMixin, StaticRefFilterEnvironmentMixin +): """Determine branch filtering statically based on config.""" def _get_regex(self, config, key): @@ -3107,17 +3277,25 @@ class ConfigRefFilterEnvironmentMixin( items.append(i) if items == []: return None - return '|'.join(items) + return "|".join(items) def __init__(self, config, **kw): super(ConfigRefFilterEnvironmentMixin, self).__init__( config=config, - ref_filter_incl_regex=self._get_regex(config, 'refFilterInclusionRegex'), - ref_filter_excl_regex=self._get_regex(config, 'refFilterExclusionRegex'), - ref_filter_do_send_regex=self._get_regex(config, 'refFilterDoSendRegex'), - ref_filter_dont_send_regex=self._get_regex(config, 'refFilterDontSendRegex'), + ref_filter_incl_regex=self._get_regex( + config, "refFilterInclusionRegex" + ), + ref_filter_excl_regex=self._get_regex( + config, "refFilterExclusionRegex" + ), + ref_filter_do_send_regex=self._get_regex( + config, "refFilterDoSendRegex" + ), + ref_filter_dont_send_regex=self._get_regex( + config, "refFilterDontSendRegex" + ), **kw - ) + ) class ProjectdescEnvironmentMixin(Environment): @@ -3128,30 +3306,36 @@ class ProjectdescEnvironmentMixin(Environment): def __init__(self, **kw): super(ProjectdescEnvironmentMixin, self).__init__(**kw) - self.COMPUTED_KEYS += ['projectdesc'] + self.COMPUTED_KEYS += ["projectdesc"] def get_projectdesc(self): """Return a one-line descripition of the project.""" git_dir = get_git_dir() try: - projectdesc = open(os.path.join(git_dir, 'description')).readline().strip() - if projectdesc and not projectdesc.startswith('Unnamed repository'): + projectdesc = ( + open(os.path.join(git_dir, "description")).readline().strip() + ) + if projectdesc and not projectdesc.startswith( + "Unnamed repository" + ): return projectdesc except IOError: pass - return 'UNNAMED PROJECT' + return "UNNAMED PROJECT" class GenericEnvironmentMixin(Environment): def get_pusher(self): - return self.osenv.get('USER', self.osenv.get('USERNAME', 'unknown user')) + return self.osenv.get( + "USER", self.osenv.get("USERNAME", "unknown user") + ) class GitoliteEnvironmentHighPrecMixin(Environment): def get_pusher(self): - return self.osenv.get('GL_USER', 'unknown user') + return self.osenv.get("GL_USER", "unknown user") class GitoliteEnvironmentLowPrecMixin(Environment): @@ -3160,36 +3344,40 @@ class GitoliteEnvironmentLowPrecMixin(Environment): # repo_shortname (though it's probably not as good as a value # the user might have explicitly put in his config). return ( - self.osenv.get('GL_REPO', None) or - super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname() - ) + self.osenv.get("GL_REPO", None) + or super( + GitoliteEnvironmentLowPrecMixin, self + ).get_repo_shortname() + ) def get_fromaddr(self, change=None): - GL_USER = self.osenv.get('GL_USER') + GL_USER = self.osenv.get("GL_USER") if GL_USER is not None: # Find the path to gitolite.conf. Note that gitolite v3 # did away with the GL_ADMINDIR and GL_CONF environment # variables (they are now hard-coded). GL_ADMINDIR = self.osenv.get( - 'GL_ADMINDIR', - os.path.expanduser(os.path.join('~', '.gitolite'))) + "GL_ADMINDIR", + os.path.expanduser(os.path.join("~", ".gitolite")), + ) GL_CONF = self.osenv.get( - 'GL_CONF', - os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf')) + "GL_CONF", os.path.join(GL_ADMINDIR, "conf", "gitolite.conf") + ) if os.path.isfile(GL_CONF): - f = open(GL_CONF, 'rU') + f = open(GL_CONF, "rU") try: in_user_emails_section = False - re_template = r'^\s*#\s*%s\s*$' + re_template = r"^\s*#\s*%s\s*$" re_begin, re_user, re_end = ( re.compile(re_template % x) for x in ( - r'BEGIN\s+USER\s+EMAILS', - re.escape(GL_USER) + r'\s+(.*)', - r'END\s+USER\s+EMAILS', - )) + r"BEGIN\s+USER\s+EMAILS", + re.escape(GL_USER) + r"\s+(.*)", + r"END\s+USER\s+EMAILS", + ) + ) for l in f: - l = l.rstrip('\n') + l = l.rstrip("\n") if not in_user_emails_section: if re_begin.match(l): in_user_emails_section = True @@ -3201,7 +3389,9 @@ class GitoliteEnvironmentLowPrecMixin(Environment): return m.group(1) finally: f.close() - return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change) + return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr( + change + ) class IncrementalDateTime(object): @@ -3224,13 +3414,14 @@ class IncrementalDateTime(object): class StashEnvironmentHighPrecMixin(Environment): def __init__(self, user=None, repo=None, **kw): - super(StashEnvironmentHighPrecMixin, - self).__init__(user=user, repo=repo, **kw) + super(StashEnvironmentHighPrecMixin, self).__init__( + user=user, repo=repo, **kw + ) self.__user = user self.__repo = repo def get_pusher(self): - return re.match('(.*?)\s*<', self.__user).group(1) + return re.match("(.*?)\s*<", self.__user).group(1) def get_pusher_email(self): return self.__user @@ -3251,20 +3442,21 @@ class StashEnvironmentLowPrecMixin(Environment): class GerritEnvironmentHighPrecMixin(Environment): def __init__(self, project=None, submitter=None, update_method=None, **kw): - super(GerritEnvironmentHighPrecMixin, - self).__init__(submitter=submitter, project=project, **kw) + super(GerritEnvironmentHighPrecMixin, self).__init__( + submitter=submitter, project=project, **kw + ) self.__project = project self.__submitter = submitter self.__update_method = update_method "Make an 'update_method' value available for templates." - self.COMPUTED_KEYS += ['update_method'] + self.COMPUTED_KEYS += ["update_method"] def get_pusher(self): if self.__submitter: - if self.__submitter.find('<') != -1: + if self.__submitter.find("<") != -1: # Submitter has a configured email, we transformed # __submitter into an RFC 2822 string already. - return re.match('(.*?)\s*<', self.__submitter).group(1) + return re.match("(.*?)\s*<", self.__submitter).group(1) else: # Submitter has no configured email, it's just his name. return self.__submitter @@ -3275,29 +3467,36 @@ class GerritEnvironmentHighPrecMixin(Environment): # merge/push was done by the Gerrit user. It was technically # triggered by someone else, but sadly we have no way of # determining who that someone else is at this point. - return 'Gerrit' # 'unknown user'? + return "Gerrit" # 'unknown user'? def get_pusher_email(self): if self.__submitter: return self.__submitter else: - return super(GerritEnvironmentHighPrecMixin, self).get_pusher_email() + return super( + GerritEnvironmentHighPrecMixin, self + ).get_pusher_email() def get_default_ref_ignore_regex(self): - default = super(GerritEnvironmentHighPrecMixin, self).get_default_ref_ignore_regex() - return default + '|^refs/changes/|^refs/cache-automerge/|^refs/meta/' + default = super( + GerritEnvironmentHighPrecMixin, self + ).get_default_ref_ignore_regex() + return default + "|^refs/changes/|^refs/cache-automerge/|^refs/meta/" def get_revision_recipients(self, revision): # Merge commits created by Gerrit when users hit "Submit this patchset" # in the Web UI (or do equivalently with REST APIs or the gerrit review # command) are not something users want to see an individual email for. # Filter them out. - committer = read_git_output(['log', '--no-walk', '--format=%cN', - revision.rev.sha1]) - if committer == 'Gerrit Code Review': + committer = read_git_output( + ["log", "--no-walk", "--format=%cN", revision.rev.sha1] + ) + if committer == "Gerrit Code Review": return [] else: - return super(GerritEnvironmentHighPrecMixin, self).get_revision_recipients(revision) + return super( + GerritEnvironmentHighPrecMixin, self + ).get_revision_recipients(revision) def get_update_method(self): return self.__update_method @@ -3313,10 +3512,12 @@ class GerritEnvironmentLowPrecMixin(Environment): return self.__project def get_fromaddr(self, change=None): - if self.__submitter and self.__submitter.find('<') != -1: + if self.__submitter and self.__submitter.find("<") != -1: return self.__submitter else: - return super(GerritEnvironmentLowPrecMixin, self).get_fromaddr(change) + return super(GerritEnvironmentLowPrecMixin, self).get_fromaddr( + change + ) class Push(object): @@ -3385,21 +3586,24 @@ class Push(object): # following order thus causes commits to be grouped with branch # changes (as opposed to tag changes) if possible. SORT_ORDER = dict( - (value, i) for (i, value) in enumerate([ - (BranchChange, 'update'), - (BranchChange, 'create'), - (AnnotatedTagChange, 'update'), - (AnnotatedTagChange, 'create'), - (NonAnnotatedTagChange, 'update'), - (NonAnnotatedTagChange, 'create'), - (BranchChange, 'delete'), - (AnnotatedTagChange, 'delete'), - (NonAnnotatedTagChange, 'delete'), - (OtherReferenceChange, 'update'), - (OtherReferenceChange, 'create'), - (OtherReferenceChange, 'delete'), - ]) + (value, i) + for (i, value) in enumerate( + [ + (BranchChange, "update"), + (BranchChange, "create"), + (AnnotatedTagChange, "update"), + (AnnotatedTagChange, "create"), + (NonAnnotatedTagChange, "update"), + (NonAnnotatedTagChange, "create"), + (BranchChange, "delete"), + (AnnotatedTagChange, "delete"), + (NonAnnotatedTagChange, "delete"), + (OtherReferenceChange, "update"), + (OtherReferenceChange, "create"), + (OtherReferenceChange, "delete"), + ] ) + ) def __init__(self, environment, changes, ignore_other_refs=False): self.changes = sorted(changes, key=self._sort_key) @@ -3412,7 +3616,10 @@ class Push(object): @classmethod def _sort_key(klass, change): - return (klass.SORT_ORDER[change.__class__, change.change_type], change.refname,) + return ( + klass.SORT_ORDER[change.__class__, change.change_type], + change.refname, + ) @property def _other_ref_sha1s(self): @@ -3420,26 +3627,30 @@ class Push(object): """ if self.__other_ref_sha1s is None: # The refnames being changed by this push: - updated_refs = set( - change.refname - for change in self.changes - ) + updated_refs = set(change.refname for change in self.changes) # The SHA-1s of commits referred to by all references in this # repository *except* updated_refs: sha1s = set() fmt = ( - '%(objectname) %(objecttype) %(refname)\n' - '%(*objectname) %(*objecttype) %(refname)' - ) - ref_filter_regex, is_inclusion_filter = \ + "%(objectname) %(objecttype) %(refname)\n" + "%(*objectname) %(*objecttype) %(refname)" + ) + ref_filter_regex, is_inclusion_filter = ( self.environment.get_ref_filter_regex() + ) for line in read_git_lines( - ['for-each-ref', '--format=%s' % (fmt,)]): - (sha1, type, name) = line.split(' ', 2) - if (sha1 and type == 'commit' and - name not in updated_refs and - include_ref(name, ref_filter_regex, is_inclusion_filter)): + ["for-each-ref", "--format=%s" % (fmt,)] + ): + (sha1, type, name) = line.split(" ", 2) + if ( + sha1 + and type == "commit" + and name not in updated_refs + and include_ref( + name, ref_filter_regex, is_inclusion_filter + ) + ): sha1s.add(sha1) self.__other_ref_sha1s = sha1s @@ -3473,7 +3684,7 @@ class Push(object): getattr(change, new_or_old).sha1 for change in self.changes if getattr(change, new_or_old) - ) + ) if not incl_spec: incl_spec = None elif not getattr(reference_change, new_or_old).commit_sha1: @@ -3498,13 +3709,13 @@ class Push(object): repository before the push. If 'old', the commits to be excluded are those that are currently in the repository. """ - old_or_new = {'old': 'new', 'new': 'old'}[new_or_old] + old_or_new = {"old": "new", "new": "old"}[new_or_old] excl_revs = self._other_ref_sha1s.union( getattr(change, old_or_new).sha1 for change in self.changes - if getattr(change, old_or_new).type in ['commit', 'tag'] - ) - return ['^' + sha1 for sha1 in sorted(excl_revs)] + if getattr(change, old_or_new).type in ["commit", "tag"] + ) + return ["^" + sha1 for sha1 in sorted(excl_revs)] def get_commits_spec(self, new_or_old, reference_change=None): """Get rev-list arguments for added or discarded commits. @@ -3543,7 +3754,7 @@ class Push(object): reference_change is None, then return a list of *all* commits added by this push.""" - spec = self.get_commits_spec('new', reference_change) + spec = self.get_commits_spec("new", reference_change) return git_rev_list(spec) def get_discarded_commits(self, reference_change): @@ -3553,7 +3764,7 @@ class Push(object): entirely discarded from the repository by the part of this push represented by reference_change.""" - spec = self.get_commits_spec('old', reference_change) + spec = self.get_commits_spec("old", reference_change) return git_rev_list(spec) def send_emails(self, mailer, body_filter=None): @@ -3581,22 +3792,26 @@ class Push(object): # Check if we've got anyone to send to if not change.recipients: change.environment.log_warning( - '*** no recipients configured so no email will be sent\n' - '*** for %r update %s->%s' - % (change.refname, change.old.sha1, change.new.sha1,) - ) + "*** no recipients configured so no email will be sent\n" + "*** for %r update %s->%s" + % (change.refname, change.old.sha1, change.new.sha1) + ) else: if not change.environment.quiet: change.environment.log_msg( - 'Sending notification emails to: %s' % (change.recipients,)) - extra_values = {'send_date': next(send_date)} + "Sending notification emails to: %s" + % (change.recipients,) + ) + extra_values = {"send_date": next(send_date)} rev = change.send_single_combined_email(sha1s) if rev: mailer.send( - change.generate_combined_email(self, rev, body_filter, extra_values), + change.generate_combined_email( + self, rev, body_filter, extra_values + ), rev.recipients, - ) + ) # This change is now fully handled; no need to handle # individual revisions any further. continue @@ -3604,37 +3819,40 @@ class Push(object): mailer.send( change.generate_email(self, body_filter, extra_values), change.recipients, - ) + ) max_emails = change.environment.maxcommitemails if max_emails and len(sha1s) > max_emails: change.environment.log_warning( - '*** Too many new commits (%d), not sending commit emails.\n' % len(sha1s) + - '*** Try setting multimailhook.maxCommitEmails to a greater value\n' + - '*** Currently, multimailhook.maxCommitEmails=%d' % max_emails - ) + "*** Too many new commits (%d), not sending commit emails.\n" + % len(sha1s) + + "*** Try setting multimailhook.maxCommitEmails to a greater value\n" + + "*** Currently, multimailhook.maxCommitEmails=%d" + % max_emails + ) return for (num, sha1) in enumerate(sha1s): - rev = Revision(change, GitObject(sha1), num=num + 1, tot=len(sha1s)) + rev = Revision( + change, GitObject(sha1), num=num + 1, tot=len(sha1s) + ) if not rev.recipients and rev.cc_recipients: - change.environment.log_msg('*** Replacing Cc: with To:') + change.environment.log_msg("*** Replacing Cc: with To:") rev.recipients = rev.cc_recipients rev.cc_recipients = None if rev.recipients: - extra_values = {'send_date': next(send_date)} + extra_values = {"send_date": next(send_date)} mailer.send( rev.generate_email(self, body_filter, extra_values), rev.recipients, - ) + ) # Consistency check: if unhandled_sha1s: change.environment.log_error( - 'ERROR: No emails were sent for the following new commits:\n' - ' %s' - % ('\n '.join(sorted(unhandled_sha1s)),) - ) + "ERROR: No emails were sent for the following new commits:\n" + " %s" % ("\n ".join(sorted(unhandled_sha1s)),) + ) def include_ref(refname, ref_filter_regex, is_inclusion_filter): @@ -3647,36 +3865,49 @@ def include_ref(refname, ref_filter_regex, is_inclusion_filter): def run_as_post_receive_hook(environment, mailer): environment.check() - send_filter_regex, send_is_inclusion_filter = environment.get_ref_filter_regex(True) - ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(False) + send_filter_regex, send_is_inclusion_filter = environment.get_ref_filter_regex( + True + ) + ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex( + False + ) changes = [] while True: line = read_line(sys.stdin) - if line == '': + if line == "": break - (oldrev, newrev, refname) = line.strip().split(' ', 2) + (oldrev, newrev, refname) = line.strip().split(" ", 2) environment.get_logger().debug( - "run_as_post_receive_hook: oldrev=%s, newrev=%s, refname=%s" % - (oldrev, newrev, refname)) + "run_as_post_receive_hook: oldrev=%s, newrev=%s, refname=%s" + % (oldrev, newrev, refname) + ) if not include_ref(refname, ref_filter_regex, is_inclusion_filter): continue - if not include_ref(refname, send_filter_regex, send_is_inclusion_filter): + if not include_ref( + refname, send_filter_regex, send_is_inclusion_filter + ): continue changes.append( ReferenceChange.create(environment, oldrev, newrev, refname) - ) + ) if changes: push = Push(environment, changes) push.send_emails(mailer, body_filter=environment.filter_body) - if hasattr(mailer, '__del__'): + if hasattr(mailer, "__del__"): mailer.__del__() -def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False): +def run_as_update_hook( + environment, mailer, refname, oldrev, newrev, force_send=False +): environment.check() - send_filter_regex, send_is_inclusion_filter = environment.get_ref_filter_regex(True) - ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(False) + send_filter_regex, send_is_inclusion_filter = environment.get_ref_filter_regex( + True + ) + ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex( + False + ) if not include_ref(refname, ref_filter_regex, is_inclusion_filter): return if not include_ref(refname, send_filter_regex, send_is_inclusion_filter): @@ -3684,37 +3915,47 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send= changes = [ ReferenceChange.create( environment, - read_git_output(['rev-parse', '--verify', oldrev]), - read_git_output(['rev-parse', '--verify', newrev]), + read_git_output(["rev-parse", "--verify", oldrev]), + read_git_output(["rev-parse", "--verify", newrev]), refname, - ), - ] + ) + ] push = Push(environment, changes, force_send) push.send_emails(mailer, body_filter=environment.filter_body) - if hasattr(mailer, '__del__'): + if hasattr(mailer, "__del__"): mailer.__del__() def check_ref_filter(environment): - send_filter_regex, send_is_inclusion = environment.get_ref_filter_regex(True) - ref_filter_regex, ref_is_inclusion = environment.get_ref_filter_regex(False) + send_filter_regex, send_is_inclusion = environment.get_ref_filter_regex( + True + ) + ref_filter_regex, ref_is_inclusion = environment.get_ref_filter_regex( + False + ) def inc_exc_lusion(b): if b: - return 'inclusion' + return "inclusion" else: - return 'exclusion' + return "exclusion" if send_filter_regex: - sys.stdout.write("DoSend/DontSend filter regex (" + - (inc_exc_lusion(send_is_inclusion)) + - '): ' + send_filter_regex.pattern + - '\n') + sys.stdout.write( + "DoSend/DontSend filter regex (" + + (inc_exc_lusion(send_is_inclusion)) + + "): " + + send_filter_regex.pattern + + "\n" + ) if send_filter_regex: - sys.stdout.write("Include/Exclude filter regex (" + - (inc_exc_lusion(ref_is_inclusion)) + - '): ' + ref_filter_regex.pattern + - '\n') + sys.stdout.write( + "Include/Exclude filter regex (" + + (inc_exc_lusion(ref_is_inclusion)) + + "): " + + ref_filter_regex.pattern + + "\n" + ) sys.stdout.write(os.linesep) sys.stdout.write( @@ -3726,28 +3967,29 @@ def check_ref_filter(environment): "refFilterExclusionRegex. Emails will be sent for commits included in these\n" "refs only when the commit reaches a ref which isn't excluded.\n" "Refs marked as DO-SEND are not excluded by any filter. Emails will\n" - "be sent normally for commits included in these refs.\n") + "be sent normally for commits included in these refs.\n" + ) sys.stdout.write(os.linesep) - for refname in read_git_lines(['for-each-ref', '--format', '%(refname)']): + for refname in read_git_lines(["for-each-ref", "--format", "%(refname)"]): sys.stdout.write(refname) if not include_ref(refname, ref_filter_regex, ref_is_inclusion): - sys.stdout.write(' EXCLUDE') + sys.stdout.write(" EXCLUDE") elif not include_ref(refname, send_filter_regex, send_is_inclusion): - sys.stdout.write(' DONT-SEND') + sys.stdout.write(" DONT-SEND") else: - sys.stdout.write(' DO-SEND') + sys.stdout.write(" DO-SEND") sys.stdout.write(os.linesep) def show_env(environment, out): - out.write('Environment values:\n') + out.write("Environment values:\n") for (k, v) in sorted(environment.get_values().items()): if k: # Don't show the {'' : ''} pair. - out.write(' %s : %r\n' % (k, v)) - out.write('\n') + out.write(" %s : %r\n" % (k, v)) + out.write("\n") # Flush to avoid interleaving with further log output out.flush() @@ -3755,9 +3997,13 @@ def show_env(environment, out): def check_setup(environment): environment.check() show_env(environment, sys.stdout) - sys.stdout.write("Now, checking that git-multimail's standard input " - "is properly set ..." + os.linesep) - sys.stdout.write("Please type some text and then press Return" + os.linesep) + sys.stdout.write( + "Now, checking that git-multimail's standard input " + "is properly set ..." + os.linesep + ) + sys.stdout.write( + "Please type some text and then press Return" + os.linesep + ) stdin = sys.stdin.readline() sys.stdout.write("You have just entered:" + os.linesep) sys.stdout.write(stdin) @@ -3765,58 +4011,77 @@ def check_setup(environment): def choose_mailer(config, environment): - mailer = config.get('mailer', default='sendmail') - - if mailer == 'smtp': - smtpserver = config.get('smtpserver', default='localhost') - smtpservertimeout = float(config.get('smtpservertimeout', default=10.0)) - smtpserverdebuglevel = int(config.get('smtpserverdebuglevel', default=0)) - smtpencryption = config.get('smtpencryption', default='none') - smtpuser = config.get('smtpuser', default='') - smtppass = config.get('smtppass', default='') - smtpcacerts = config.get('smtpcacerts', default='') + mailer = config.get("mailer", default="sendmail") + + if mailer == "smtp": + smtpserver = config.get("smtpserver", default="localhost") + smtpservertimeout = float( + config.get("smtpservertimeout", default=10.0) + ) + smtpserverdebuglevel = int( + config.get("smtpserverdebuglevel", default=0) + ) + smtpencryption = config.get("smtpencryption", default="none") + smtpuser = config.get("smtpuser", default="") + smtppass = config.get("smtppass", default="") + smtpcacerts = config.get("smtpcacerts", default="") mailer = SMTPMailer( environment, - envelopesender=(environment.get_sender() or environment.get_fromaddr()), - smtpserver=smtpserver, smtpservertimeout=smtpservertimeout, + envelopesender=( + environment.get_sender() or environment.get_fromaddr() + ), + smtpserver=smtpserver, + smtpservertimeout=smtpservertimeout, smtpserverdebuglevel=smtpserverdebuglevel, smtpencryption=smtpencryption, smtpuser=smtpuser, smtppass=smtppass, - smtpcacerts=smtpcacerts - ) - elif mailer == 'sendmail': - command = config.get('sendmailcommand') + smtpcacerts=smtpcacerts, + ) + elif mailer == "sendmail": + command = config.get("sendmailcommand") if command: command = shlex.split(command) - mailer = SendMailer(environment, - command=command, envelopesender=environment.get_sender()) + mailer = SendMailer( + environment, + command=command, + envelopesender=environment.get_sender(), + ) else: environment.log_error( - 'fatal: multimailhook.mailer is set to an incorrect value: "%s"\n' % mailer + - 'please use one of "smtp" or "sendmail".' - ) + 'fatal: multimailhook.mailer is set to an incorrect value: "%s"\n' + % mailer + + 'please use one of "smtp" or "sendmail".' + ) sys.exit(1) return mailer KNOWN_ENVIRONMENTS = { - 'generic': {'highprec': GenericEnvironmentMixin}, - 'gitolite': {'highprec': GitoliteEnvironmentHighPrecMixin, - 'lowprec': GitoliteEnvironmentLowPrecMixin}, - 'stash': {'highprec': StashEnvironmentHighPrecMixin, - 'lowprec': StashEnvironmentLowPrecMixin}, - 'gerrit': {'highprec': GerritEnvironmentHighPrecMixin, - 'lowprec': GerritEnvironmentLowPrecMixin}, - } - - -def choose_environment(config, osenv=None, env=None, recipients=None, - hook_info=None): + "generic": {"highprec": GenericEnvironmentMixin}, + "gitolite": { + "highprec": GitoliteEnvironmentHighPrecMixin, + "lowprec": GitoliteEnvironmentLowPrecMixin, + }, + "stash": { + "highprec": StashEnvironmentHighPrecMixin, + "lowprec": StashEnvironmentLowPrecMixin, + }, + "gerrit": { + "highprec": GerritEnvironmentHighPrecMixin, + "lowprec": GerritEnvironmentLowPrecMixin, + }, +} + + +def choose_environment( + config, osenv=None, env=None, recipients=None, hook_info=None +): env_name = choose_environment_name(config, env, osenv) environment_klass = build_environment_klass(env_name) - env = build_environment(environment_klass, env_name, config, - osenv, recipients, hook_info) + env = build_environment( + environment_klass, env_name, config, osenv, recipients, hook_info + ) return env @@ -3825,13 +4090,13 @@ def choose_environment_name(config, env, osenv): osenv = os.environ if not env: - env = config.get('environment') + env = config.get("environment") if not env: - if 'GL_USER' in osenv and 'GL_REPO' in osenv: - env = 'gitolite' + if "GL_USER" in osenv and "GL_REPO" in osenv: + env = "gitolite" else: - env = 'generic' + env = "generic" return env @@ -3845,55 +4110,49 @@ COMMON_ENVIRONMENT_MIXINS = [ ConfigFilterLinesEnvironmentMixin, PusherDomainEnvironmentMixin, ConfigOptionsEnvironmentMixin, - ] +] def build_environment_klass(env_name): - if 'class' in KNOWN_ENVIRONMENTS[env_name]: - return KNOWN_ENVIRONMENTS[env_name]['class'] + if "class" in KNOWN_ENVIRONMENTS[env_name]: + return KNOWN_ENVIRONMENTS[env_name]["class"] environment_mixins = [] known_env = KNOWN_ENVIRONMENTS[env_name] - if 'highprec' in known_env: - high_prec_mixin = known_env['highprec'] + if "highprec" in known_env: + high_prec_mixin = known_env["highprec"] environment_mixins.append(high_prec_mixin) environment_mixins = environment_mixins + COMMON_ENVIRONMENT_MIXINS - if 'lowprec' in known_env: - low_prec_mixin = known_env['lowprec'] + if "lowprec" in known_env: + low_prec_mixin = known_env["lowprec"] environment_mixins.append(low_prec_mixin) environment_mixins.append(Environment) - klass_name = env_name.capitalize() + 'Environement' - environment_klass = type( - klass_name, - tuple(environment_mixins), - {}, - ) - KNOWN_ENVIRONMENTS[env_name]['class'] = environment_klass + klass_name = env_name.capitalize() + "Environement" + environment_klass = type(klass_name, tuple(environment_mixins), {}) + KNOWN_ENVIRONMENTS[env_name]["class"] = environment_klass return environment_klass -GerritEnvironment = build_environment_klass('gerrit') -StashEnvironment = build_environment_klass('stash') -GitoliteEnvironment = build_environment_klass('gitolite') -GenericEnvironment = build_environment_klass('generic') +GerritEnvironment = build_environment_klass("gerrit") +StashEnvironment = build_environment_klass("stash") +GitoliteEnvironment = build_environment_klass("gitolite") +GenericEnvironment = build_environment_klass("generic") -def build_environment(environment_klass, env, config, - osenv, recipients, hook_info): - environment_kw = { - 'osenv': osenv, - 'config': config, - } +def build_environment( + environment_klass, env, config, osenv, recipients, hook_info +): + environment_kw = {"osenv": osenv, "config": config} - if env == 'stash': - environment_kw['user'] = hook_info['stash_user'] - environment_kw['repo'] = hook_info['stash_repo'] - elif env == 'gerrit': - environment_kw['project'] = hook_info['project'] - environment_kw['submitter'] = hook_info['submitter'] - environment_kw['update_method'] = hook_info['update_method'] + if env == "stash": + environment_kw["user"] = hook_info["stash_user"] + environment_kw["repo"] = hook_info["stash_repo"] + elif env == "gerrit": + environment_kw["project"] = hook_info["project"] + environment_kw["submitter"] = hook_info["submitter"] + environment_kw["update_method"] = hook_info["update_method"] - environment_kw['cli_recipients'] = recipients + environment_kw["cli_recipients"] = recipients return environment_klass(**environment_kw) @@ -3903,11 +4162,11 @@ def get_version(): try: try: os.chdir(os.path.dirname(os.path.realpath(__file__))) - git_version = read_git_output(['describe', '--tags', 'HEAD']) + git_version = read_git_output(["describe", "--tags", "HEAD"]) if git_version == __version__: return git_version else: - return '%s (%s)' % (__version__, git_version) + return "%s (%s)" % (__version__, git_version) except: pass finally: @@ -3915,28 +4174,35 @@ def get_version(): return __version__ -def compute_gerrit_options(options, args, required_gerrit_options, - raw_refname): +def compute_gerrit_options( + options, args, required_gerrit_options, raw_refname +): if None in required_gerrit_options: - raise SystemExit("Error: Specify all of --oldrev, --newrev, --refname, " - "and --project; or none of them.") + raise SystemExit( + "Error: Specify all of --oldrev, --newrev, --refname, " + "and --project; or none of them." + ) - if options.environment not in (None, 'gerrit'): - raise SystemExit("Non-gerrit environments incompatible with --oldrev, " - "--newrev, --refname, and --project") - options.environment = 'gerrit' + if options.environment not in (None, "gerrit"): + raise SystemExit( + "Non-gerrit environments incompatible with --oldrev, " + "--newrev, --refname, and --project" + ) + options.environment = "gerrit" if args: - raise SystemExit("Error: Positional parameters not allowed with " - "--oldrev, --newrev, and --refname.") + raise SystemExit( + "Error: Positional parameters not allowed with " + "--oldrev, --newrev, and --refname." + ) # Gerrit oddly omits 'refs/heads/' in the refname when calling # ref-updated hook; put it back. git_dir = get_git_dir() - if (not os.path.exists(os.path.join(git_dir, raw_refname)) and - os.path.exists(os.path.join(git_dir, 'refs', 'heads', - raw_refname))): - options.refname = 'refs/heads/' + options.refname + if not os.path.exists( + os.path.join(git_dir, raw_refname) + ) and os.path.exists(os.path.join(git_dir, "refs", "heads", raw_refname)): + options.refname = "refs/heads/" + options.refname # New revisions can appear in a gerrit repository either due to someone # pushing directly (in which case options.submitter will be set), or they @@ -3948,12 +4214,14 @@ def compute_gerrit_options(options, args, required_gerrit_options, # gerrit review command in with "Submit this patchset" button, since they # have the same effect. if options.submitter: - update_method = 'pushed' + update_method = "pushed" # The submitter argument is almost an RFC 2822 email address; change it # from 'User Name (email@domain)' to 'User Name ' so it is - options.submitter = options.submitter.replace('(', '<').replace(')', '>') + options.submitter = options.submitter.replace("(", "<").replace( + ")", ">" + ) else: - update_method = 'submitted' + update_method = "submitted" # Gerrit knew who submitted this patchset, but threw that information # away when it invoked this hook. However, *IF* Gerrit created a # merge to bring the patchset in (project 'Submit Type' is either @@ -3963,49 +4231,83 @@ def compute_gerrit_options(options, args, required_gerrit_options, # person who requested the submission of the CR. Since this is fairly # likely for most gerrit installations (of a reasonable size), it's # worth the extra effort to try to determine the actual submitter. - rev_info = read_git_lines(['log', '--no-walk', '--merges', - '--format=%cN%n%aN <%aE>', options.newrev]) - if rev_info and rev_info[0] == 'Gerrit Code Review': + rev_info = read_git_lines( + [ + "log", + "--no-walk", + "--merges", + "--format=%cN%n%aN <%aE>", + options.newrev, + ] + ) + if rev_info and rev_info[0] == "Gerrit Code Review": options.submitter = rev_info[1] # We pass back refname, oldrev, newrev as args because then the # gerrit ref-updated hook is much like the git update hook - return (options, - [options.refname, options.oldrev, options.newrev], - {'project': options.project, 'submitter': options.submitter, - 'update_method': update_method}) + return ( + options, + [options.refname, options.oldrev, options.newrev], + { + "project": options.project, + "submitter": options.submitter, + "update_method": update_method, + }, + ) def check_hook_specific_args(options, args): raw_refname = options.refname # Convert each string option unicode for Python3. if PYTHON3: - opts = ['environment', 'recipients', 'oldrev', 'newrev', 'refname', - 'project', 'submitter', 'stash_user', 'stash_repo'] + opts = [ + "environment", + "recipients", + "oldrev", + "newrev", + "refname", + "project", + "submitter", + "stash_user", + "stash_repo", + ] for opt in opts: if not hasattr(options, opt): continue obj = getattr(options, opt) if obj: - enc = obj.encode('utf-8', 'surrogateescape') - dec = enc.decode('utf-8', 'replace') + enc = obj.encode("utf-8", "surrogateescape") + dec = enc.decode("utf-8", "replace") setattr(options, opt, dec) # First check for stash arguments if (options.stash_user is None) != (options.stash_repo is None): - raise SystemExit("Error: Specify both of --stash-user and " - "--stash-repo or neither.") + raise SystemExit( + "Error: Specify both of --stash-user and " + "--stash-repo or neither." + ) if options.stash_user: - options.environment = 'stash' - return options, args, {'stash_user': options.stash_user, - 'stash_repo': options.stash_repo} + options.environment = "stash" + return ( + options, + args, + { + "stash_user": options.stash_user, + "stash_repo": options.stash_repo, + }, + ) # Finally, check for gerrit specific arguments - required_gerrit_options = (options.oldrev, options.newrev, options.refname, - options.project) + required_gerrit_options = ( + options.oldrev, + options.newrev, + options.refname, + options.project, + ) if required_gerrit_options != (None,) * 4: - return compute_gerrit_options(options, args, required_gerrit_options, - raw_refname) + return compute_gerrit_options( + options, args, required_gerrit_options, raw_refname + ) # No special options in use, just return what we started with return options, args, {} @@ -4021,7 +4323,9 @@ class Logger(object): def create_log_file(self, environment, name, path, verbosity): log_file = logging.getLogger(name) file_handler = logging.FileHandler(path) - log_fmt = logging.Formatter("%(asctime)s [%(levelname)-5.5s] %(message)s") + log_fmt = logging.Formatter( + "%(asctime)s [%(levelname)-5.5s] %(message)s" + ) file_handler.setFormatter(log_fmt) log_file.addHandler(file_handler) log_file.setLevel(verbosity) @@ -4030,7 +4334,7 @@ class Logger(object): def __init__(self, environment): self.environment = environment self.loggers = [] - stderr_log = logging.getLogger('git_multimail.stderr') + stderr_log = logging.getLogger("git_multimail.stderr") class EncodedStderr(object): def write(self, x): @@ -4046,17 +4350,29 @@ class Logger(object): if environment.debug_log_file is not None: debug_log_file = self.create_log_file( - environment, 'git_multimail.debug', environment.debug_log_file, logging.DEBUG) + environment, + "git_multimail.debug", + environment.debug_log_file, + logging.DEBUG, + ) self.loggers.append(debug_log_file) if environment.log_file is not None: log_file = self.create_log_file( - environment, 'git_multimail.file', environment.log_file, logging.INFO) + environment, + "git_multimail.file", + environment.log_file, + logging.INFO, + ) self.loggers.append(log_file) if environment.error_log_file is not None: error_log_file = self.create_log_file( - environment, 'git_multimail.error', environment.error_log_file, logging.ERROR) + environment, + "git_multimail.error", + environment.error_log_file, + logging.ERROR, + ) self.loggers.append(error_log_file) def info(self, msg): @@ -4079,69 +4395,86 @@ class Logger(object): def main(args): parser = optparse.OptionParser( description=__doc__, - usage='%prog [OPTIONS]\n or: %prog [OPTIONS] REFNAME OLDREV NEWREV', - ) + usage="%prog [OPTIONS]\n or: %prog [OPTIONS] REFNAME OLDREV NEWREV", + ) parser.add_option( - '--environment', '--env', action='store', type='choice', - choices=list(KNOWN_ENVIRONMENTS.keys()), default=None, + "--environment", + "--env", + action="store", + type="choice", + choices=list(KNOWN_ENVIRONMENTS.keys()), + default=None, help=( - 'Choose type of environment is in use. Default is taken from ' + "Choose type of environment is in use. Default is taken from " 'multimailhook.environment if set; otherwise "generic".' - ), - ) + ), + ) parser.add_option( - '--stdout', action='store_true', default=False, - help='Output emails to stdout rather than sending them.', - ) + "--stdout", + action="store_true", + default=False, + help="Output emails to stdout rather than sending them.", + ) parser.add_option( - '--recipients', action='store', default=None, - help='Set list of email recipients for all types of emails.', - ) + "--recipients", + action="store", + default=None, + help="Set list of email recipients for all types of emails.", + ) parser.add_option( - '--show-env', action='store_true', default=False, + "--show-env", + action="store_true", + default=False, help=( - 'Write to stderr the values determined for the environment ' - '(intended for debugging purposes), then proceed normally.' - ), - ) + "Write to stderr the values determined for the environment " + "(intended for debugging purposes), then proceed normally." + ), + ) parser.add_option( - '--force-send', action='store_true', default=False, + "--force-send", + action="store_true", + default=False, help=( - 'Force sending refchange email when using as an update hook. ' - 'This is useful to work around the unreliable new commits ' - 'detection in this mode.' - ), - ) + "Force sending refchange email when using as an update hook. " + "This is useful to work around the unreliable new commits " + "detection in this mode." + ), + ) parser.add_option( - '-c', metavar="=", action='append', + "-c", + metavar="=", + action="append", help=( - 'Pass a configuration parameter through to git. The value given ' - 'will override values from configuration files. See the -c option ' - 'of git(1) for more details. (Only works with git >= 1.7.3)' - ), - ) + "Pass a configuration parameter through to git. The value given " + "will override values from configuration files. See the -c option " + "of git(1) for more details. (Only works with git >= 1.7.3)" + ), + ) parser.add_option( - '--version', '-v', action='store_true', default=False, - help=( - "Display git-multimail's version" - ), - ) + "--version", + "-v", + action="store_true", + default=False, + help=("Display git-multimail's version"), + ) parser.add_option( - '--python-version', action='store_true', default=False, - help=( - "Display the version of Python used by git-multimail" - ), - ) + "--python-version", + action="store_true", + default=False, + help=("Display the version of Python used by git-multimail"), + ) parser.add_option( - '--check-ref-filter', action='store_true', default=False, + "--check-ref-filter", + action="store_true", + default=False, help=( - 'List refs and show information on how git-multimail ' - 'will process them.' - ) - ) + "List refs and show information on how git-multimail " + "will process them." + ), + ) # The following options permit this script to be run as a gerrit # ref-updated hook. See e.g. @@ -4149,43 +4482,50 @@ def main(args): # We suppress help for these items, since these are specific to gerrit, # and we don't want users directly using them any way other than how the # gerrit ref-updated hook is called. - parser.add_option('--oldrev', action='store', help=optparse.SUPPRESS_HELP) - parser.add_option('--newrev', action='store', help=optparse.SUPPRESS_HELP) - parser.add_option('--refname', action='store', help=optparse.SUPPRESS_HELP) - parser.add_option('--project', action='store', help=optparse.SUPPRESS_HELP) - parser.add_option('--submitter', action='store', help=optparse.SUPPRESS_HELP) + parser.add_option("--oldrev", action="store", help=optparse.SUPPRESS_HELP) + parser.add_option("--newrev", action="store", help=optparse.SUPPRESS_HELP) + parser.add_option("--refname", action="store", help=optparse.SUPPRESS_HELP) + parser.add_option("--project", action="store", help=optparse.SUPPRESS_HELP) + parser.add_option( + "--submitter", action="store", help=optparse.SUPPRESS_HELP + ) # The following allow this to be run as a stash asynchronous post-receive # hook (almost identical to a git post-receive hook but triggered also for # merges of pull requests from the UI). We suppress help for these items, # since these are specific to stash. - parser.add_option('--stash-user', action='store', help=optparse.SUPPRESS_HELP) - parser.add_option('--stash-repo', action='store', help=optparse.SUPPRESS_HELP) + parser.add_option( + "--stash-user", action="store", help=optparse.SUPPRESS_HELP + ) + parser.add_option( + "--stash-repo", action="store", help=optparse.SUPPRESS_HELP + ) (options, args) = parser.parse_args(args) (options, args, hook_info) = check_hook_specific_args(options, args) if options.version: - sys.stdout.write('git-multimail version ' + get_version() + '\n') + sys.stdout.write("git-multimail version " + get_version() + "\n") return if options.python_version: - sys.stdout.write('Python version ' + sys.version + '\n') + sys.stdout.write("Python version " + sys.version + "\n") return if options.c: Config.add_config_parameters(options.c) - config = Config('multimailhook') + config = Config("multimailhook") environment = None try: environment = choose_environment( - config, osenv=os.environ, + config, + osenv=os.environ, env=options.environment, recipients=options.recipients, hook_info=hook_info, - ) + ) if options.show_env: show_env(environment, sys.stderr) @@ -4195,8 +4535,8 @@ def main(args): else: mailer = choose_mailer(config, environment) - must_check_setup = os.environ.get('GIT_MULTIMAIL_CHECK_SETUP') - if must_check_setup == '': + must_check_setup = os.environ.get("GIT_MULTIMAIL_CHECK_SETUP") + if must_check_setup == "": must_check_setup = False if options.check_ref_filter: check_ref_filter(environment) @@ -4206,12 +4546,20 @@ def main(args): # like an update hook; otherwise, run as a post-receive hook. elif args: if len(args) != 3: - parser.error('Need zero or three non-option arguments') + parser.error("Need zero or three non-option arguments") (refname, oldrev, newrev) = args environment.get_logger().debug( - "run_as_update_hook: refname=%s, oldrev=%s, newrev=%s, force_send=%s" % - (refname, oldrev, newrev, options.force_send)) - run_as_update_hook(environment, mailer, refname, oldrev, newrev, options.force_send) + "run_as_update_hook: refname=%s, oldrev=%s, newrev=%s, force_send=%s" + % (refname, oldrev, newrev, options.force_send) + ) + run_as_update_hook( + environment, + mailer, + refname, + oldrev, + newrev, + options.force_send, + ) else: run_as_post_receive_hook(environment, mailer) except ConfigurationException: @@ -4221,20 +4569,23 @@ def main(args): except Exception: t, e, tb = sys.exc_info() import traceback - sys.stderr.write('\n') # Avoid mixing message with previous output + + sys.stderr.write("\n") # Avoid mixing message with previous output msg = ( - 'Exception \'' + t.__name__ + - '\' raised. Please report this as a bug to\n' - 'https://github.com/git-multimail/git-multimail/issues\n' - 'with the information below:\n\n' - 'git-multimail version ' + get_version() + '\n' - 'Python version ' + sys.version + '\n' + - traceback.format_exc()) + "Exception '" + + t.__name__ + + "' raised. Please report this as a bug to\n" + "https://github.com/git-multimail/git-multimail/issues\n" + "with the information below:\n\n" + "git-multimail version " + get_version() + "\n" + "Python version " + sys.version + "\n" + traceback.format_exc() + ) try: environment.get_logger().error(msg) except: sys.stderr.write(msg) sys.exit(1) -if __name__ == '__main__': + +if __name__ == "__main__": main(sys.argv[1:])