mirror of
https://github.com/git/git.git
synced 2024-05-28 01:06:12 +02:00
git-multimail: update to release 1.4.0
Changes are described in CHANGES. Contributions-by: Matthieu Moy <Matthieu.Moy@imag.fr> Contributions-by: Irfan Adilovic <irfanadilovic@gmail.com> Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
07d1a42bad
commit
7c5543115e
|
@ -1,3 +1,62 @@
|
|||
Release 1.4.0
|
||||
=============
|
||||
|
||||
New features to troubleshoot a git-multimail installation
|
||||
---------------------------------------------------------
|
||||
|
||||
* One can now perform a basic check of git-multimail's setup by
|
||||
running the hook with the environment variable
|
||||
GIT_MULTIMAIL_CHECK_SETUP set to a non-empty string. See
|
||||
doc/troubleshooting.rst for details.
|
||||
|
||||
* A new log files system was added. See the multimailhook.logFile,
|
||||
multimailhook.errorLogFile and multimailhook.debugLogFile variables.
|
||||
|
||||
* git_multimail.py can now be made more verbose using
|
||||
multimailhook.verbose.
|
||||
|
||||
* A new option --check-ref-filter is now available to help debugging
|
||||
the refFilter* options.
|
||||
|
||||
Formatting emails
|
||||
-----------------
|
||||
|
||||
* Formatting of emails was made slightly more compact, to reduce the
|
||||
odds of having long subject lines truncated or wrapped in short list
|
||||
of commits.
|
||||
|
||||
* multimailhook.emailPrefix may now use the '%(repo_shortname)s'
|
||||
placeholder for the repository's short name.
|
||||
|
||||
* A new option multimailhook.subjectMaxLength is available to truncate
|
||||
overly long subject lines.
|
||||
|
||||
Bug fixes and minor changes
|
||||
---------------------------
|
||||
|
||||
* Options refFilterDoSendRegex and refFilterDontSendRegex were
|
||||
essentially broken. They should work now.
|
||||
|
||||
* The behavior when both refFilter{Do,Dont}SendRegex and
|
||||
refFilter{Exclusion,Inclusion}Regex are set have been slightly
|
||||
changed. Exclusion/Inclusion is now strictly stronger than
|
||||
DoSend/DontSend.
|
||||
|
||||
* The management of precedence when a setting can be computed in
|
||||
multiple ways has been considerably refactored and modified.
|
||||
multimailhook.from and multimailhook.reponame now have precedence
|
||||
over the environment-specific settings ($GL_REPO/$GL_USER for
|
||||
gitolite, --stash-user/repo for Stash, --submitter/--project for
|
||||
Gerrit).
|
||||
|
||||
* The coverage of the testsuite has been considerably improved. All
|
||||
configuration variables now appear at least once in the testsuite.
|
||||
|
||||
This version was tested with Python 2.6 to 3.5. It also mostly works
|
||||
with Python 2.4, but there is one known breakage in the testsuite
|
||||
related to non-ascii characters. It was tested with Git
|
||||
1.7.10.406.gdc801, 1.8.5.6, 2.1.4, and 2.10.0.rc0.1.g07c9292.
|
||||
|
||||
Release 1.3.1 (bugfix-only release)
|
||||
===================================
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@ Contributing
|
|||
git-multimail is an open-source project, built by volunteers. We would
|
||||
welcome your help!
|
||||
|
||||
The current maintainers are Michael Haggerty <mhagger@alum.mit.edu>
|
||||
and Matthieu Moy <matthieu.moy@grenoble-inp.fr>.
|
||||
The current maintainers are Matthieu Moy
|
||||
<matthieu.moy@grenoble-inp.fr> and Michael Haggerty
|
||||
<mhagger@alum.mit.edu>.
|
||||
|
||||
Please note that although a copy of git-multimail is distributed in
|
||||
the "contrib" section of the main Git project, development takes place
|
||||
|
@ -22,6 +23,10 @@ to the maintainers). Please sign off your patches as per the `Git
|
|||
project practice
|
||||
<https://github.com/git/git/blob/master/Documentation/SubmittingPatches#L234>`__.
|
||||
|
||||
Please vote for issues you would like to be addressed in priority
|
||||
(click "add your reaction" and then the "+1" thumbs-up button on the
|
||||
GitHub issue).
|
||||
|
||||
General discussion of git-multimail can take place on the main `Git
|
||||
mailing list`_.
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
git-multimail 1.3.1
|
||||
===================
|
||||
git-multimail version 1.4.0
|
||||
===========================
|
||||
|
||||
.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
|
||||
:target: https://travis-ci.org/git-multimail/git-multimail
|
||||
|
||||
git-multimail is a tool for sending notification emails on pushes to a
|
||||
Git repository. It includes a Python module called git_multimail.py,
|
||||
Git repository. It includes a Python module called ``git_multimail.py``,
|
||||
which can either be used as a hook script directly or can be imported
|
||||
as a Python module into another script.
|
||||
|
||||
|
@ -93,20 +93,20 @@ Requirements
|
|||
Invocation
|
||||
----------
|
||||
|
||||
git_multimail.py is designed to be used as a ``post-receive`` hook in a
|
||||
``git_multimail.py`` is designed to be used as a ``post-receive`` hook in a
|
||||
Git repository (see githooks(5)). Link or copy it to
|
||||
$GIT_DIR/hooks/post-receive within the repository for which email
|
||||
notifications are desired. Usually it should be installed on the
|
||||
central repository for a project, to which all commits are eventually
|
||||
pushed.
|
||||
|
||||
For use on pre-v1.5.1 Git servers, git_multimail.py can also work as
|
||||
For use on pre-v1.5.1 Git servers, ``git_multimail.py`` can also work as
|
||||
an ``update`` hook, taking its arguments on the command line. To use
|
||||
this script in this manner, link or copy it to $GIT_DIR/hooks/update.
|
||||
Please note that the script is not completely reliable in this mode
|
||||
[2]_.
|
||||
[1]_.
|
||||
|
||||
Alternatively, git_multimail.py can be imported as a Python module
|
||||
Alternatively, ``git_multimail.py`` can be imported as a Python module
|
||||
into your own Python post-receive script. This method is a bit more
|
||||
work, but allows the behavior of the hook to be customized using
|
||||
arbitrary Python code. For example, you can use a custom environment
|
||||
|
@ -122,7 +122,7 @@ arbitrary Python code. For example, you can use a custom environment
|
|||
|
||||
Or you can change how emails are sent by writing your own Mailer
|
||||
class. The ``post-receive`` script in this directory demonstrates how
|
||||
to use git_multimail.py as a Python module. (If you make interesting
|
||||
to use ``git_multimail.py`` as a Python module. (If you make interesting
|
||||
changes of this type, please consider sharing them with the
|
||||
community.)
|
||||
|
||||
|
@ -151,7 +151,10 @@ multimailhook.environment
|
|||
the repository name is derived from the repository's path.
|
||||
|
||||
gitolite
|
||||
the username of the pusher is read from $GL_USER, the repository
|
||||
Environment to use when ``git-multimail`` is ran as a gitolite_
|
||||
hook.
|
||||
|
||||
The username of the pusher is read from $GL_USER, the repository
|
||||
name is read from $GL_REPO, and the From: header value is
|
||||
optionally read from gitolite.conf (see multimailhook.from).
|
||||
|
||||
|
@ -444,7 +447,9 @@ multimailhook.emailPrefix
|
|||
email filtering (though filtering based on the X-Git-* email
|
||||
headers is probably more robust). Default is the short name of
|
||||
the repository in square brackets; e.g., ``[myrepo]``. Set this
|
||||
value to the empty string to suppress the email prefix.
|
||||
value to the empty string to suppress the email prefix. You may
|
||||
use the placeholder ``%(repo_shortname)s`` for the short name of
|
||||
the repository.
|
||||
|
||||
multimailhook.emailMaxLines
|
||||
The maximum number of lines that should be included in the body of
|
||||
|
@ -461,6 +466,17 @@ multimailhook.emailMaxLineLength
|
|||
lines, the diffs are probably unreadable anyway. To disable line
|
||||
truncation, set this option to 0.
|
||||
|
||||
multimailhook.subjectMaxLength
|
||||
The maximum length of the subject line (i.e. the ``oneline`` field
|
||||
in templates, not including the prefix). Lines longer than this
|
||||
limit are truncated to this length with a trailing ``[...]`` added
|
||||
to indicate the missing text. This option The default is to use
|
||||
``multimailhook.emailMaxLineLength``. This option avoids sending
|
||||
emails with overly long subject lines, but should not be needed if
|
||||
the commit messages follow the Git convention (one short subject
|
||||
line, then a blank line, then the message body). To disable line
|
||||
truncation, set this option to 0.
|
||||
|
||||
multimailhook.maxCommitEmails
|
||||
The maximum number of commit emails to send for a given change.
|
||||
When the number of patches is larger that this value, only the
|
||||
|
@ -474,12 +490,15 @@ multimailhook.emailStrictUTF8
|
|||
not valid UTF-8 are converted to the Unicode replacement
|
||||
character, U+FFFD. The default is `true`.
|
||||
|
||||
This option is ineffective with Python 3, where non-UTF-8
|
||||
characters are unconditionally replaced.
|
||||
|
||||
multimailhook.diffOpts
|
||||
Options passed to ``git diff-tree`` when generating the summary
|
||||
information for ReferenceChange emails. Default is ``--stat
|
||||
--summary --find-copies-harder``. Add -p to those options to
|
||||
include a unified diff of changes in addition to the usual summary
|
||||
output. Shell quoting is allowed; see multimailhook.logOpts for
|
||||
output. Shell quoting is allowed; see ``multimailhook.logOpts`` for
|
||||
details.
|
||||
|
||||
multimailhook.graphOpts
|
||||
|
@ -564,6 +583,8 @@ multimailhook.refFilterInclusionRegex, multimailhook.refFilterExclusionRegex, mu
|
|||
the user-interface is not stable yet (in particular, the option
|
||||
names may change). If you want to participate in stabilizing the
|
||||
feature, please contact the maintainers and/or send pull-requests.
|
||||
If you are happy with the current shape of the feature, please
|
||||
report it too.
|
||||
|
||||
Regular expressions that can be used to limit refs for which email
|
||||
updates will be sent. It is an error to specify both an inclusion
|
||||
|
@ -613,6 +634,32 @@ multimailhook.refFilterInclusionRegex, multimailhook.refFilterExclusionRegex, mu
|
|||
[multimailhook]
|
||||
refFilterExclusionRegex = ^refs/tags/|^refs/heads/master$
|
||||
|
||||
``refFilterInclusionRegex`` and ``refFilterExclusionRegex`` are
|
||||
strictly stronger than ``refFilterDoSendRegex`` and
|
||||
``refFilterDontSendRegex``. In other words, adding a ref to a
|
||||
DoSend/DontSend regex has no effect if it is already excluded by a
|
||||
Exclusion/Inclusion regex.
|
||||
|
||||
multimailhook.logFile, multimailhook.errorLogFile, multimailhook.debugLogFile
|
||||
|
||||
When set, these variable designate path to files where
|
||||
git-multimail will log some messages. Normal messages and error
|
||||
messages are sent to ``logFile``, and error messages are also sent
|
||||
to ``errorLogFile``. Debug messages and all other messages are
|
||||
sent to ``debugLogFile``. The recommended way is to set only one
|
||||
of these variables, but it is also possible to set several of them
|
||||
(part of the information is then duplicated in several log files,
|
||||
for example errors are duplicated to all log files).
|
||||
|
||||
Relative path are relative to the Git repository where the push is
|
||||
done.
|
||||
|
||||
multimailhook.verbose
|
||||
|
||||
Verbosity level of git-multimail on its standard output. By
|
||||
default, show only error and info messages. If set to true, show
|
||||
also debug messages.
|
||||
|
||||
Email filtering aids
|
||||
--------------------
|
||||
|
||||
|
@ -628,8 +675,8 @@ Customizing email contents
|
|||
|
||||
git-multimail mostly generates emails by expanding templates. The
|
||||
templates can be customized. To avoid the need to edit
|
||||
git_multimail.py directly, the preferred way to change the templates
|
||||
is to write a separate Python script that imports git_multimail.py as
|
||||
``git_multimail.py`` directly, the preferred way to change the templates
|
||||
is to write a separate Python script that imports ``git_multimail.py`` as
|
||||
a module, then replaces the templates in place. See the provided
|
||||
post-receive script for an example of how this is done.
|
||||
|
||||
|
@ -645,8 +692,8 @@ GenericEnvironment
|
|||
a stand-alone Git repository.
|
||||
|
||||
GitoliteEnvironment
|
||||
a Git repository that is managed by gitolite
|
||||
[3]_. For such repositories, the identity of the pusher is read from
|
||||
a Git repository that is managed by gitolite_. For such
|
||||
repositories, the identity of the pusher is read from
|
||||
environment variable $GL_USER, the name of the repository is read
|
||||
from $GL_REPO (if it is not overridden by multimailhook.reponame),
|
||||
and the From: header value is optionally read from gitolite.conf
|
||||
|
@ -662,7 +709,7 @@ option to the script.
|
|||
If you need to customize the script in ways that are not supported by
|
||||
the existing environments, you can define your own environment class
|
||||
class using arbitrary Python code. To do so, you need to import
|
||||
git_multimail.py as a Python module, as demonstrated by the example
|
||||
``git_multimail.py`` as a Python module, as demonstrated by the example
|
||||
post-receive script. Then implement your environment class; it should
|
||||
usually inherit from one of the existing Environment classes and
|
||||
possibly one or more of the EnvironmentMixin classes. Then set the
|
||||
|
@ -690,9 +737,7 @@ contribute to git-multimail.
|
|||
Footnotes
|
||||
---------
|
||||
|
||||
.. [1] http://www.python.org/dev/peps/pep-0394/
|
||||
|
||||
.. [2] Because of the way information is passed to update hooks, the
|
||||
.. [1] Because of the way information is passed to update hooks, the
|
||||
script's method of determining whether a commit has already
|
||||
been seen does not work when it is used as an ``update`` script.
|
||||
In particular, no notification email will be generated for a
|
||||
|
@ -700,4 +745,4 @@ Footnotes
|
|||
push. A workaround is to use --force-send to force sending the
|
||||
emails.
|
||||
|
||||
.. [3] https://github.com/sitaramc/gitolite
|
||||
.. _gitolite: https://github.com/sitaramc/gitolite
|
||||
|
|
|
@ -6,10 +6,10 @@ website:
|
|||
https://github.com/git-multimail/git-multimail
|
||||
|
||||
The version in this directory was obtained from the upstream project
|
||||
on May 13 2016 and consists of the "git-multimail" subdirectory from
|
||||
on August 17 2016 and consists of the "git-multimail" subdirectory from
|
||||
revision
|
||||
|
||||
3ce5470d4abf7251604cbf64e73a962e1b617f5e refs/tags/1.3.1
|
||||
07b1cb6bfd7be156c62e1afa17cae13b850a869f refs/tags/1.4.0
|
||||
|
||||
Please see the README file in this directory for information about how
|
||||
to report bugs or contribute to git-multimail.
|
||||
|
|
|
@ -1,6 +1,40 @@
|
|||
Troubleshooting issues with git-multimail: a FAQ
|
||||
================================================
|
||||
|
||||
How to check that git-multimail is properly set up?
|
||||
---------------------------------------------------
|
||||
|
||||
Since version 1.4.0, git-multimail allows a simple self-checking of
|
||||
its configuration: run it with the environment variable
|
||||
``GIT_MULTIMAIL_CHECK_SETUP`` set to a non-empty string. You should
|
||||
get something like this::
|
||||
|
||||
$ GIT_MULTIMAIL_CHECK_SETUP=true /home/moy/dev/git-multimail/git-multimail/git_multimail.py
|
||||
Environment values:
|
||||
administrator : 'the administrator of this repository'
|
||||
charset : 'utf-8'
|
||||
emailprefix : '[git-multimail] '
|
||||
fqdn : 'anie'
|
||||
projectdesc : 'UNNAMED PROJECT'
|
||||
pusher : 'moy'
|
||||
repo_path : '/home/moy/dev/git-multimail'
|
||||
repo_shortname : 'git-multimail'
|
||||
|
||||
Now, checking that git-multimail's standard input is properly set ...
|
||||
Please type some text and then press Return
|
||||
foo
|
||||
You have just entered:
|
||||
foo
|
||||
git-multimail seems properly set up.
|
||||
|
||||
If you forgot to set an important variable, you may get instead::
|
||||
|
||||
$ GIT_MULTIMAIL_CHECK_SETUP=true /home/moy/dev/git-multimail/git-multimail/git_multimail.py
|
||||
No email recipients configured!
|
||||
|
||||
Do not set ``$GIT_MULTIMAIL_CHECK_SETUP`` other than for testing your
|
||||
configuration: it would disable the hook completely.
|
||||
|
||||
Git is not using the right address in the From/To/Reply-To field
|
||||
----------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
__version__ = '1.3.1'
|
||||
__version__ = '1.4.0'
|
||||
|
||||
# Copyright (c) 2015 Matthieu Moy and others
|
||||
# Copyright (c) 2015-2016 Matthieu Moy and others
|
||||
# Copyright (c) 2012-2014 Michael Haggerty and others
|
||||
# Derived from contrib/hooks/post-receive-email, which is
|
||||
# Copyright (c) 2007 Andy Parkins
|
||||
|
@ -56,6 +56,7 @@
|
|||
import subprocess
|
||||
import shlex
|
||||
import optparse
|
||||
import logging
|
||||
import smtplib
|
||||
try:
|
||||
import ssl
|
||||
|
@ -86,8 +87,8 @@ def is_string(s):
|
|||
def str_to_bytes(s):
|
||||
return s.encode(ENCODING)
|
||||
|
||||
def bytes_to_str(s):
|
||||
return s.decode(ENCODING)
|
||||
def bytes_to_str(s, errors='strict'):
|
||||
return s.decode(ENCODING, errors)
|
||||
|
||||
unicode = str
|
||||
|
||||
|
@ -98,6 +99,15 @@ def write_str(f, msg):
|
|||
f.buffer.write(msg.encode(sys.getdefaultencoding()))
|
||||
except UnicodeEncodeError:
|
||||
f.buffer.write(msg.encode(ENCODING))
|
||||
|
||||
def read_line(f):
|
||||
# Try reading with the default encoding. If it fails,
|
||||
# try UTF-8.
|
||||
out = f.buffer.readline()
|
||||
try:
|
||||
return out.decode(sys.getdefaultencoding())
|
||||
except UnicodeEncodeError:
|
||||
return out.decode(ENCODING)
|
||||
else:
|
||||
def is_string(s):
|
||||
try:
|
||||
|
@ -108,12 +118,15 @@ def is_string(s):
|
|||
def str_to_bytes(s):
|
||||
return s
|
||||
|
||||
def bytes_to_str(s):
|
||||
def bytes_to_str(s, errors='strict'):
|
||||
return s
|
||||
|
||||
def write_str(f, msg):
|
||||
f.write(msg)
|
||||
|
||||
def read_line(f):
|
||||
return f.readline()
|
||||
|
||||
def next(it):
|
||||
return it.next()
|
||||
|
||||
|
@ -213,8 +226,8 @@ def next(it):
|
|||
\\
|
||||
O -- O -- O (%(oldrev_short)s)
|
||||
|
||||
Any revisions marked "omits" are not gone; other references still
|
||||
refer to them. Any revisions marked "discards" are gone forever.
|
||||
Any revisions marked "omit" are not gone; other references still
|
||||
refer to them. Any revisions marked "discard" are gone forever.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -233,8 +246,8 @@ def next(it):
|
|||
revisions, and so the following emails describe only the N revisions
|
||||
from the common base, B.
|
||||
|
||||
Any revisions marked "omits" are not gone; other references still
|
||||
refer to them. Any revisions marked "discards" are gone forever.
|
||||
Any revisions marked "omit" are not gone; other references still
|
||||
refer to them. Any revisions marked "discard" are gone forever.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -258,22 +271,22 @@ def next(it):
|
|||
NEW_REVISIONS_TEMPLATE = """\
|
||||
The %(tot)s revisions listed above as "new" are entirely new to this
|
||||
repository and will be described in separate emails. The revisions
|
||||
listed as "adds" were already present in the repository and have only
|
||||
listed as "add" were already present in the repository and have only
|
||||
been added to this reference.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
TAG_CREATED_TEMPLATE = """\
|
||||
at %(newrev_short)-9s (%(newrev_type)s)
|
||||
at %(newrev_short)-8s (%(newrev_type)s)
|
||||
"""
|
||||
|
||||
|
||||
TAG_UPDATED_TEMPLATE = """\
|
||||
*** WARNING: tag %(short_refname)s was modified! ***
|
||||
|
||||
from %(oldrev_short)-9s (%(oldrev_type)s)
|
||||
to %(newrev_short)-9s (%(newrev_type)s)
|
||||
from %(oldrev_short)-8s (%(oldrev_type)s)
|
||||
to %(newrev_short)-8s (%(newrev_type)s)
|
||||
"""
|
||||
|
||||
|
||||
|
@ -286,7 +299,7 @@ def next(it):
|
|||
# The template used in summary tables. It looks best if this uses the
|
||||
# same alignment as TAG_CREATED_TEMPLATE and TAG_UPDATED_TEMPLATE.
|
||||
BRIEF_SUMMARY_TEMPLATE = """\
|
||||
%(action)10s %(rev_short)-9s %(text)s
|
||||
%(action)8s %(rev_short)-8s %(text)s
|
||||
"""
|
||||
|
||||
|
||||
|
@ -434,11 +447,16 @@ 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']
|
||||
p = subprocess.Popen(
|
||||
cmd, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw
|
||||
tuple(str_to_bytes(w) for w in cmd),
|
||||
stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw
|
||||
)
|
||||
(out, err) = p.communicate(input)
|
||||
out = bytes_to_str(out)
|
||||
out = bytes_to_str(out, errors=errors)
|
||||
retcode = p.wait()
|
||||
if retcode:
|
||||
raise CommandError(cmd, retcode)
|
||||
|
@ -1020,7 +1038,9 @@ def generate_email(self, push, body_filter=None, extra_header_values={}):
|
|||
for line in footer:
|
||||
yield line
|
||||
|
||||
def get_alt_fromaddr(self):
|
||||
def get_specific_fromaddr(self):
|
||||
"""For kinds of Changes which specify it, return the kind-specific
|
||||
From address to use."""
|
||||
return None
|
||||
|
||||
|
||||
|
@ -1045,7 +1065,7 @@ def __init__(self, reference_change, rev, num, tot):
|
|||
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\n' % (self.cc_recipients, self.rev.sha1))
|
||||
'Add %s to CC for %s' % (self.cc_recipients, self.rev.sha1))
|
||||
|
||||
def _cc_recipients(self):
|
||||
cc_recipients = []
|
||||
|
@ -1065,6 +1085,10 @@ def _compute_values(self):
|
|||
['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
|
||||
|
@ -1121,7 +1145,7 @@ def generate_email_body(self, push):
|
|||
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: '):]
|
||||
else:
|
||||
|
@ -1135,7 +1159,7 @@ 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)
|
||||
|
||||
def get_alt_fromaddr(self):
|
||||
def get_specific_fromaddr(self):
|
||||
return self.environment.from_commit
|
||||
|
||||
|
||||
|
@ -1193,7 +1217,7 @@ def create(environment, oldrev, newrev, refname):
|
|||
# Tracking branch:
|
||||
environment.log_warning(
|
||||
'*** Push-update of tracking branch %r\n'
|
||||
'*** - incomplete email generated.\n'
|
||||
'*** - incomplete email generated.'
|
||||
% (refname,)
|
||||
)
|
||||
klass = OtherReferenceChange
|
||||
|
@ -1201,7 +1225,7 @@ def create(environment, oldrev, newrev, refname):
|
|||
# Some other reference namespace:
|
||||
environment.log_warning(
|
||||
'*** Push-update of strange reference %r\n'
|
||||
'*** - incomplete email generated.\n'
|
||||
'*** - incomplete email generated.'
|
||||
% (refname,)
|
||||
)
|
||||
klass = OtherReferenceChange
|
||||
|
@ -1209,7 +1233,7 @@ def create(environment, oldrev, newrev, refname):
|
|||
# Anything else (is there anything else?)
|
||||
environment.log_warning(
|
||||
'*** Unknown type of update to %r (%s)\n'
|
||||
'*** - incomplete email generated.\n'
|
||||
'*** - incomplete email generated.'
|
||||
% (refname, rev.type,)
|
||||
)
|
||||
klass = OtherReferenceChange
|
||||
|
@ -1446,9 +1470,9 @@ def generate_revision_change_summary(self, push):
|
|||
if discards and adds:
|
||||
for (sha1, subject) in discards:
|
||||
if sha1 in discarded_commits:
|
||||
action = 'discards'
|
||||
action = 'discard'
|
||||
else:
|
||||
action = 'omits'
|
||||
action = 'omit'
|
||||
yield self.expand(
|
||||
BRIEF_SUMMARY_TEMPLATE, action=action,
|
||||
rev_short=sha1, text=subject,
|
||||
|
@ -1457,7 +1481,7 @@ def generate_revision_change_summary(self, push):
|
|||
if sha1 in new_commits:
|
||||
action = 'new'
|
||||
else:
|
||||
action = 'adds'
|
||||
action = 'add'
|
||||
yield self.expand(
|
||||
BRIEF_SUMMARY_TEMPLATE, action=action,
|
||||
rev_short=sha1, text=subject,
|
||||
|
@ -1469,9 +1493,9 @@ def generate_revision_change_summary(self, push):
|
|||
elif discards:
|
||||
for (sha1, subject) in discards:
|
||||
if sha1 in discarded_commits:
|
||||
action = 'discards'
|
||||
action = 'discard'
|
||||
else:
|
||||
action = 'omits'
|
||||
action = 'omit'
|
||||
yield self.expand(
|
||||
BRIEF_SUMMARY_TEMPLATE, action=action,
|
||||
rev_short=sha1, text=subject,
|
||||
|
@ -1490,7 +1514,7 @@ def generate_revision_change_summary(self, push):
|
|||
if sha1 in new_commits:
|
||||
action = 'new'
|
||||
else:
|
||||
action = 'adds'
|
||||
action = 'add'
|
||||
yield self.expand(
|
||||
BRIEF_SUMMARY_TEMPLATE, action=action,
|
||||
rev_short=sha1, text=subject,
|
||||
|
@ -1543,7 +1567,7 @@ def generate_revision_change_summary(self, push):
|
|||
for r in discarded_revisions:
|
||||
(sha1, subject) = r.rev.get_summary()
|
||||
yield r.expand(
|
||||
BRIEF_SUMMARY_TEMPLATE, action='discards', text=subject,
|
||||
BRIEF_SUMMARY_TEMPLATE, action='discard', text=subject,
|
||||
)
|
||||
for line in self.generate_revision_change_graph(push):
|
||||
yield line
|
||||
|
@ -1581,7 +1605,7 @@ def generate_delete_summary(self, push):
|
|||
)
|
||||
yield '\n'
|
||||
|
||||
def get_alt_fromaddr(self):
|
||||
def get_specific_fromaddr(self):
|
||||
return self.environment.from_refchange
|
||||
|
||||
|
||||
|
@ -1791,13 +1815,13 @@ def describe_tag(self, push):
|
|||
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 ' tagged by %s\n' % (tagger,)
|
||||
yield ' on %s\n' % (tagged,)
|
||||
yield ' by %s\n' % (tagger,)
|
||||
yield ' on %s\n' % (tagged,)
|
||||
yield '\n'
|
||||
|
||||
# Show the content of the tag message; this might contain a
|
||||
|
@ -1914,6 +1938,9 @@ def __init__(self, environment, refname, short_refname, old, new, rev):
|
|||
class Mailer(object):
|
||||
"""An object that can send emails."""
|
||||
|
||||
def __init__(self, environment):
|
||||
self.environment = environment
|
||||
|
||||
def send(self, lines, to_addrs):
|
||||
"""Send an email consisting of lines.
|
||||
|
||||
|
@ -1948,14 +1975,14 @@ def find_sendmail():
|
|||
'Try setting multimailhook.sendmailCommand.'
|
||||
)
|
||||
|
||||
def __init__(self, command=None, envelopesender=None):
|
||||
def __init__(self, environment, command=None, envelopesender=None):
|
||||
"""Construct a SendMailer instance.
|
||||
|
||||
command should be the command and arguments used to invoke
|
||||
sendmail, as a list of strings. If an envelopesender is
|
||||
provided, it will also be passed to the command, via '-f
|
||||
envelopesender'."""
|
||||
|
||||
super(SendMailer, self).__init__(environment)
|
||||
if command:
|
||||
self.command = command[:]
|
||||
else:
|
||||
|
@ -1968,7 +1995,7 @@ def send(self, lines, to_addrs):
|
|||
try:
|
||||
p = subprocess.Popen(self.command, stdin=subprocess.PIPE)
|
||||
except OSError:
|
||||
sys.stderr.write(
|
||||
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' +
|
||||
|
@ -1979,15 +2006,16 @@ def send(self, lines, to_addrs):
|
|||
lines = (str_to_bytes(line) for line in lines)
|
||||
p.stdin.writelines(lines)
|
||||
except Exception:
|
||||
sys.stderr.write(
|
||||
self.environment.get_logger().error(
|
||||
'*** Error while generating commit email\n'
|
||||
'*** - mail sending aborted.\n'
|
||||
)
|
||||
try:
|
||||
if hasattr(p, 'terminate'):
|
||||
# subprocess.terminate() is not available in Python 2.4
|
||||
p.terminate()
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
import signal
|
||||
os.kill(p.pid, signal.SIGTERM)
|
||||
raise
|
||||
else:
|
||||
p.stdin.close()
|
||||
|
@ -1999,14 +2027,16 @@ def send(self, lines, to_addrs):
|
|||
class SMTPMailer(Mailer):
|
||||
"""Send emails using Python's smtplib."""
|
||||
|
||||
def __init__(self, envelopesender, smtpserver,
|
||||
def __init__(self, environment,
|
||||
envelopesender, smtpserver,
|
||||
smtpservertimeout=10.0, smtpserverdebuglevel=0,
|
||||
smtpencryption='none',
|
||||
smtpuser='', smtppass='',
|
||||
smtpcacerts=''
|
||||
):
|
||||
super(SMTPMailer, self).__init__(environment)
|
||||
if not envelopesender:
|
||||
sys.stderr.write(
|
||||
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'
|
||||
)
|
||||
|
@ -2041,7 +2071,7 @@ def call(klass, server, timeout):
|
|||
self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout)
|
||||
elif self.security == 'tls':
|
||||
if 'ssl' not in sys.modules:
|
||||
sys.stderr.write(
|
||||
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'
|
||||
|
@ -2071,7 +2101,7 @@ def call(klass, server, timeout):
|
|||
self.smtp.sock,
|
||||
cert_reqs=ssl.CERT_NONE
|
||||
)
|
||||
sys.stderr.write(
|
||||
self.environment.get_logger().error(
|
||||
'*** Warning, the server certificat is not verified (smtp) ***\n'
|
||||
'*** set the option smtpCACerts ***\n'
|
||||
)
|
||||
|
@ -2094,10 +2124,10 @@ def call(klass, server, timeout):
|
|||
% self.smtpserverdebuglevel)
|
||||
self.smtp.set_debuglevel(self.smtpserverdebuglevel)
|
||||
except Exception:
|
||||
sys.stderr.write(
|
||||
self.environment.get_logger().error(
|
||||
'*** Error establishing SMTP connection to %s ***\n'
|
||||
% self.smtpserver)
|
||||
sys.stderr.write('*** %s\n' % sys.exc_info()[1])
|
||||
'*** %s\n'
|
||||
% (self.smtpserver, sys.exc_info()[1]))
|
||||
sys.exit(1)
|
||||
|
||||
def __del__(self):
|
||||
|
@ -2115,10 +2145,11 @@ def send(self, lines, to_addrs):
|
|||
to_addrs = [email for (name, email) in getaddresses([to_addrs])]
|
||||
self.smtp.sendmail(self.envelopesender, to_addrs, msg)
|
||||
except smtplib.SMTPResponseException:
|
||||
sys.stderr.write('*** Error sending email ***\n')
|
||||
err = sys.exc_info()[1]
|
||||
sys.stderr.write('*** Error %d: %s\n' % (err.smtp_code,
|
||||
bytes_to_str(err.smtp_error)))
|
||||
self.environment.get_logger().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
|
||||
|
@ -2126,9 +2157,10 @@ def send(self, lines, to_addrs):
|
|||
del self.smtp
|
||||
smtp.quit()
|
||||
except:
|
||||
sys.stderr.write('*** Error closing the SMTP connection ***\n')
|
||||
sys.stderr.write('*** Exiting anyway ... ***\n')
|
||||
sys.stderr.write('*** %s\n' % sys.exc_info()[1])
|
||||
self.environment.get_logger().error(
|
||||
'*** Error closing the SMTP connection ***\n'
|
||||
'*** Exiting anyway ... ***\n'
|
||||
'*** %s\n' % sys.exc_info()[1])
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
@ -2250,6 +2282,11 @@ class Environment(object):
|
|||
to send and when computing what commits are considered new
|
||||
to the repository. Default is "^refs/notes/".
|
||||
|
||||
get_max_subject_length()
|
||||
|
||||
Return an int giving the maximal length for the subject
|
||||
(git log --oneline).
|
||||
|
||||
They should also define the following attributes:
|
||||
|
||||
announce_show_shortlog (bool)
|
||||
|
@ -2324,6 +2361,15 @@ class Environment(object):
|
|||
multimailhook.fromRefchange and multimailhook.fromCommit
|
||||
by ConfigEnvironmentMixin.
|
||||
|
||||
log_file, error_log_file, debug_log_file (string)
|
||||
|
||||
Name of a file to which logs should be sent.
|
||||
|
||||
verbose (int)
|
||||
|
||||
How verbose the system should be.
|
||||
- 0 (default): show info, errors, ...
|
||||
- 1 : show basic debug info
|
||||
"""
|
||||
|
||||
REPO_NAME_RE = re.compile(r'^(?P<name>.+?)(?:\.git)$')
|
||||
|
@ -2346,6 +2392,7 @@ def __init__(self, osenv=None):
|
|||
self.quiet = False
|
||||
self.stdout = False
|
||||
self.combine_when_single_commit = True
|
||||
self.logger = None
|
||||
|
||||
self.COMPUTED_KEYS = [
|
||||
'administrator',
|
||||
|
@ -2360,6 +2407,12 @@ def __init__(self, osenv=None):
|
|||
|
||||
self._values = None
|
||||
|
||||
def get_logger(self):
|
||||
"""Get (possibly creates) the logger associated to this environment."""
|
||||
if self.logger is None:
|
||||
self.logger = Logger(self)
|
||||
return self.logger
|
||||
|
||||
def get_repo_shortname(self):
|
||||
"""Use the last part of the repo path, with ".git" stripped off if present."""
|
||||
|
||||
|
@ -2467,6 +2520,11 @@ def get_default_ref_ignore_regex(self):
|
|||
# which we simply do not have right now.
|
||||
return "^refs/notes/"
|
||||
|
||||
def get_max_subject_length(self):
|
||||
"""Return the maximal subject line (git log --oneline) length.
|
||||
Longer subject lines will be truncated."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def filter_body(self, lines):
|
||||
"""Filter the lines intended for an email body.
|
||||
|
||||
|
@ -2482,19 +2540,22 @@ def log_msg(self, msg):
|
|||
"""Write the string msg on a log file or on stderr.
|
||||
|
||||
Sends the text to stderr by default, override to change the behavior."""
|
||||
write_str(sys.stderr, msg)
|
||||
self.get_logger().info(msg)
|
||||
|
||||
def log_warning(self, msg):
|
||||
"""Write the string msg on a log file or on stderr.
|
||||
|
||||
Sends the text to stderr by default, override to change the behavior."""
|
||||
write_str(sys.stderr, msg)
|
||||
self.get_logger().warning(msg)
|
||||
|
||||
def log_error(self, msg):
|
||||
"""Write the string msg on a log file or on stderr.
|
||||
|
||||
Sends the text to stderr by default, override to change the behavior."""
|
||||
write_str(sys.stderr, msg)
|
||||
self.get_logger().error(msg)
|
||||
|
||||
def check(self):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigEnvironmentMixin(Environment):
|
||||
|
@ -2613,6 +2674,14 @@ def __init__(self, config, **kw):
|
|||
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.verbose = 1
|
||||
else:
|
||||
self.verbose = 0
|
||||
|
||||
def get_administrator(self):
|
||||
return (
|
||||
self.config.get('administrator') or
|
||||
|
@ -2631,11 +2700,21 @@ def get_emailprefix(self):
|
|||
if emailprefix is not None:
|
||||
emailprefix = emailprefix.strip()
|
||||
if emailprefix:
|
||||
return emailprefix + ' '
|
||||
else:
|
||||
return ''
|
||||
emailprefix += ' '
|
||||
else:
|
||||
return '[%s] ' % (self.get_repo_shortname(),)
|
||||
emailprefix = '[%(repo_shortname)s] '
|
||||
short_name = self.get_repo_shortname()
|
||||
try:
|
||||
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"
|
||||
)
|
||||
raise ConfigurationException(
|
||||
'"%s" is not an allowed setting for emailPrefix' % emailprefix
|
||||
)
|
||||
|
||||
def get_sender(self):
|
||||
return self.config.get('envelopesender')
|
||||
|
@ -2656,9 +2735,9 @@ def process_addr(self, addr, change):
|
|||
def get_fromaddr(self, change=None):
|
||||
fromaddr = self.config.get('from')
|
||||
if change:
|
||||
alt_fromaddr = change.get_alt_fromaddr()
|
||||
if alt_fromaddr:
|
||||
fromaddr = alt_fromaddr
|
||||
specific_fromaddr = change.get_specific_fromaddr()
|
||||
if specific_fromaddr:
|
||||
fromaddr = specific_fromaddr
|
||||
if fromaddr:
|
||||
fromaddr = self.process_addr(fromaddr, change)
|
||||
if fromaddr:
|
||||
|
@ -2684,7 +2763,7 @@ def get_scancommitforcc(self):
|
|||
class FilterLinesEnvironmentMixin(Environment):
|
||||
"""Handle encoding and maximum line length of body lines.
|
||||
|
||||
emailmaxlinelength (int or None)
|
||||
email_max_line_length (int or None)
|
||||
|
||||
The maximum length of any single line in the email body.
|
||||
Longer lines are truncated at that length with ' [...]'
|
||||
|
@ -2699,10 +2778,13 @@ class FilterLinesEnvironmentMixin(Environment):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, strict_utf8=True, emailmaxlinelength=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.__emailmaxlinelength = emailmaxlinelength
|
||||
self.__email_max_line_length = email_max_line_length
|
||||
self.__max_subject_length = max_subject_length
|
||||
|
||||
def filter_body(self, lines):
|
||||
lines = super(FilterLinesEnvironmentMixin, self).filter_body(lines)
|
||||
|
@ -2711,15 +2793,18 @@ def filter_body(self, lines):
|
|||
lines = (line.decode(ENCODING, 'replace') for line in lines)
|
||||
# Limit the line length in Unicode-space to avoid
|
||||
# splitting characters:
|
||||
if self.__emailmaxlinelength:
|
||||
lines = limit_linelength(lines, self.__emailmaxlinelength)
|
||||
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)
|
||||
elif self.__emailmaxlinelength:
|
||||
lines = limit_linelength(lines, self.__emailmaxlinelength)
|
||||
elif self.__email_max_line_length:
|
||||
lines = limit_linelength(lines, self.__email_max_line_length)
|
||||
|
||||
return lines
|
||||
|
||||
def get_max_subject_length(self):
|
||||
return self.__max_subject_length
|
||||
|
||||
|
||||
class ConfigFilterLinesEnvironmentMixin(
|
||||
ConfigEnvironmentMixin,
|
||||
|
@ -2732,9 +2817,13 @@ def __init__(self, config, **kw):
|
|||
if strict_utf8 is not None:
|
||||
kw['strict_utf8'] = strict_utf8
|
||||
|
||||
emailmaxlinelength = config.get('emailmaxlinelength')
|
||||
if emailmaxlinelength is not None:
|
||||
kw['emailmaxlinelength'] = int(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)
|
||||
|
||||
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)
|
||||
|
||||
super(ConfigFilterLinesEnvironmentMixin, self).__init__(
|
||||
config=config, **kw
|
||||
|
@ -2750,7 +2839,7 @@ def __init__(self, emailmaxlines, **kw):
|
|||
|
||||
def filter_body(self, lines):
|
||||
lines = super(MaxlinesEnvironmentMixin, self).filter_body(lines)
|
||||
if self.__emailmaxlines:
|
||||
if self.__emailmaxlines > 0:
|
||||
lines = limit_lines(lines, self.__emailmaxlines)
|
||||
return lines
|
||||
|
||||
|
@ -2843,25 +2932,64 @@ def __init__(
|
|||
# actual *contents* of the change being reported, we only
|
||||
# choose based on the *type* of the change. Therefore we can
|
||||
# compute them once and for all:
|
||||
if not (refchange_recipients or
|
||||
announce_recipients or
|
||||
revision_recipients or
|
||||
scancommitforcc):
|
||||
raise ConfigurationException('No email recipients configured!')
|
||||
self.__refchange_recipients = refchange_recipients
|
||||
self.__announce_recipients = announce_recipients
|
||||
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!')
|
||||
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 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 self.__announce_recipients
|
||||
|
||||
def get_revision_recipients(self, revision):
|
||||
if self.__revision_recipients is None:
|
||||
return super(StaticRecipientsEnvironmentMixin,
|
||||
self).get_refchange_recipients(revision)
|
||||
return self.__revision_recipients
|
||||
|
||||
|
||||
class CLIRecipientsEnvironmentMixin(Environment):
|
||||
"""Mixin storing recipients information comming from the
|
||||
command-line."""
|
||||
|
||||
def __init__(self, cli_recipients=None, **kw):
|
||||
super(CLIRecipientsEnvironmentMixin, self).__init__(**kw)
|
||||
self.__cli_recipients = cli_recipients
|
||||
|
||||
def get_refchange_recipients(self, refchange):
|
||||
if self.__cli_recipients is None:
|
||||
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 self.__cli_recipients
|
||||
|
||||
def get_revision_recipients(self, revision):
|
||||
if self.__cli_recipients is None:
|
||||
return super(CLIRecipientsEnvironmentMixin,
|
||||
self).get_revision_recipients(revision)
|
||||
return self.__cli_recipients
|
||||
|
||||
|
||||
class ConfigRecipientsEnvironmentMixin(
|
||||
ConfigEnvironmentMixin,
|
||||
StaticRecipientsEnvironmentMixin
|
||||
|
@ -2935,24 +3063,20 @@ def __init__(self, ref_filter_incl_regex, ref_filter_excl_regex,
|
|||
if ref_filter_do_send_regex and ref_filter_dont_send_regex:
|
||||
raise ConfigurationException(
|
||||
"Cannot specify both a ref doSend and dontSend regex.")
|
||||
if ref_filter_do_send_regex or ref_filter_dont_send_regex:
|
||||
self.__is_do_send_filter = bool(ref_filter_do_send_regex)
|
||||
if ref_filter_incl_regex:
|
||||
ref_filter_send_regex = ref_filter_incl_regex
|
||||
elif ref_filter_excl_regex:
|
||||
ref_filter_send_regex = ref_filter_excl_regex
|
||||
else:
|
||||
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]))
|
||||
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:
|
||||
self.__send_compiled_regex = self.__compiled_regex
|
||||
self.__is_do_send_filter = self.__is_inclusion_filter
|
||||
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]))
|
||||
|
||||
def get_ref_filter_regex(self, send_filter=False):
|
||||
if send_filter:
|
||||
|
@ -3023,34 +3147,21 @@ def get_pusher(self):
|
|||
return self.osenv.get('USER', self.osenv.get('USERNAME', 'unknown user'))
|
||||
|
||||
|
||||
class GenericEnvironment(
|
||||
ProjectdescEnvironmentMixin,
|
||||
ConfigMaxlinesEnvironmentMixin,
|
||||
ComputeFQDNEnvironmentMixin,
|
||||
ConfigFilterLinesEnvironmentMixin,
|
||||
ConfigRecipientsEnvironmentMixin,
|
||||
ConfigRefFilterEnvironmentMixin,
|
||||
PusherDomainEnvironmentMixin,
|
||||
ConfigOptionsEnvironmentMixin,
|
||||
GenericEnvironmentMixin,
|
||||
Environment,
|
||||
):
|
||||
pass
|
||||
class GitoliteEnvironmentHighPrecMixin(Environment):
|
||||
def get_pusher(self):
|
||||
return self.osenv.get('GL_USER', 'unknown user')
|
||||
|
||||
|
||||
class GitoliteEnvironmentMixin(Environment):
|
||||
class GitoliteEnvironmentLowPrecMixin(Environment):
|
||||
def get_repo_shortname(self):
|
||||
# The gitolite environment variable $GL_REPO is a pretty good
|
||||
# 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(GitoliteEnvironmentMixin, self).get_repo_shortname()
|
||||
super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname()
|
||||
)
|
||||
|
||||
def get_pusher(self):
|
||||
return self.osenv.get('GL_USER', 'unknown user')
|
||||
|
||||
def get_fromaddr(self, change=None):
|
||||
GL_USER = self.osenv.get('GL_USER')
|
||||
if GL_USER is not None:
|
||||
|
@ -3088,7 +3199,7 @@ def get_fromaddr(self, change=None):
|
|||
return m.group(1)
|
||||
finally:
|
||||
f.close()
|
||||
return super(GitoliteEnvironmentMixin, self).get_fromaddr(change)
|
||||
return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change)
|
||||
|
||||
|
||||
class IncrementalDateTime(object):
|
||||
|
@ -3109,67 +3220,43 @@ def __next__(self):
|
|||
return formatted
|
||||
|
||||
|
||||
class GitoliteEnvironment(
|
||||
ProjectdescEnvironmentMixin,
|
||||
ConfigMaxlinesEnvironmentMixin,
|
||||
ComputeFQDNEnvironmentMixin,
|
||||
ConfigFilterLinesEnvironmentMixin,
|
||||
ConfigRecipientsEnvironmentMixin,
|
||||
ConfigRefFilterEnvironmentMixin,
|
||||
PusherDomainEnvironmentMixin,
|
||||
ConfigOptionsEnvironmentMixin,
|
||||
GitoliteEnvironmentMixin,
|
||||
Environment,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class StashEnvironmentMixin(Environment):
|
||||
class StashEnvironmentHighPrecMixin(Environment):
|
||||
def __init__(self, user=None, repo=None, **kw):
|
||||
super(StashEnvironmentMixin, self).__init__(**kw)
|
||||
super(StashEnvironmentHighPrecMixin,
|
||||
self).__init__(user=user, repo=repo, **kw)
|
||||
self.__user = user
|
||||
self.__repo = repo
|
||||
|
||||
def get_repo_shortname(self):
|
||||
return self.__repo
|
||||
|
||||
def get_pusher(self):
|
||||
return re.match('(.*?)\s*<', self.__user).group(1)
|
||||
|
||||
def get_pusher_email(self):
|
||||
return self.__user
|
||||
|
||||
|
||||
class StashEnvironmentLowPrecMixin(Environment):
|
||||
def __init__(self, user=None, repo=None, **kw):
|
||||
super(StashEnvironmentLowPrecMixin, self).__init__(**kw)
|
||||
self.__repo = repo
|
||||
self.__user = user
|
||||
|
||||
def get_repo_shortname(self):
|
||||
return self.__repo
|
||||
|
||||
def get_fromaddr(self, change=None):
|
||||
return self.__user
|
||||
|
||||
|
||||
class StashEnvironment(
|
||||
StashEnvironmentMixin,
|
||||
ProjectdescEnvironmentMixin,
|
||||
ConfigMaxlinesEnvironmentMixin,
|
||||
ComputeFQDNEnvironmentMixin,
|
||||
ConfigFilterLinesEnvironmentMixin,
|
||||
ConfigRecipientsEnvironmentMixin,
|
||||
ConfigRefFilterEnvironmentMixin,
|
||||
PusherDomainEnvironmentMixin,
|
||||
ConfigOptionsEnvironmentMixin,
|
||||
Environment,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class GerritEnvironmentMixin(Environment):
|
||||
class GerritEnvironmentHighPrecMixin(Environment):
|
||||
def __init__(self, project=None, submitter=None, update_method=None, **kw):
|
||||
super(GerritEnvironmentMixin, self).__init__(**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']
|
||||
|
||||
def get_repo_shortname(self):
|
||||
return self.__project
|
||||
|
||||
def get_pusher(self):
|
||||
if self.__submitter:
|
||||
if self.__submitter.find('<') != -1:
|
||||
|
@ -3192,16 +3279,10 @@ def get_pusher_email(self):
|
|||
if self.__submitter:
|
||||
return self.__submitter
|
||||
else:
|
||||
return super(GerritEnvironmentMixin, self).get_pusher_email()
|
||||
|
||||
def get_fromaddr(self, change=None):
|
||||
if self.__submitter and self.__submitter.find('<') != -1:
|
||||
return self.__submitter
|
||||
else:
|
||||
return super(GerritEnvironmentMixin, self).get_fromaddr(change)
|
||||
return super(GerritEnvironmentHighPrecMixin, self).get_pusher_email()
|
||||
|
||||
def get_default_ref_ignore_regex(self):
|
||||
default = super(GerritEnvironmentMixin, self).get_default_ref_ignore_regex()
|
||||
default = super(GerritEnvironmentHighPrecMixin, self).get_default_ref_ignore_regex()
|
||||
return default + '|^refs/changes/|^refs/cache-automerge/|^refs/meta/'
|
||||
|
||||
def get_revision_recipients(self, revision):
|
||||
|
@ -3214,25 +3295,26 @@ def get_revision_recipients(self, revision):
|
|||
if committer == 'Gerrit Code Review':
|
||||
return []
|
||||
else:
|
||||
return super(GerritEnvironmentMixin, self).get_revision_recipients(revision)
|
||||
return super(GerritEnvironmentHighPrecMixin, self).get_revision_recipients(revision)
|
||||
|
||||
def get_update_method(self):
|
||||
return self.__update_method
|
||||
|
||||
|
||||
class GerritEnvironment(
|
||||
GerritEnvironmentMixin,
|
||||
ProjectdescEnvironmentMixin,
|
||||
ConfigMaxlinesEnvironmentMixin,
|
||||
ComputeFQDNEnvironmentMixin,
|
||||
ConfigFilterLinesEnvironmentMixin,
|
||||
ConfigRecipientsEnvironmentMixin,
|
||||
ConfigRefFilterEnvironmentMixin,
|
||||
PusherDomainEnvironmentMixin,
|
||||
ConfigOptionsEnvironmentMixin,
|
||||
Environment,
|
||||
):
|
||||
pass
|
||||
class GerritEnvironmentLowPrecMixin(Environment):
|
||||
def __init__(self, project=None, submitter=None, **kw):
|
||||
super(GerritEnvironmentLowPrecMixin, self).__init__(**kw)
|
||||
self.__project = project
|
||||
self.__submitter = submitter
|
||||
|
||||
def get_repo_shortname(self):
|
||||
return self.__project
|
||||
|
||||
def get_fromaddr(self, change=None):
|
||||
if self.__submitter and self.__submitter.find('<') != -1:
|
||||
return self.__submitter
|
||||
else:
|
||||
return super(GerritEnvironmentLowPrecMixin, self).get_fromaddr(change)
|
||||
|
||||
|
||||
class Push(object):
|
||||
|
@ -3498,13 +3580,13 @@ def send_emails(self, mailer, body_filter=None):
|
|||
if not change.recipients:
|
||||
change.environment.log_warning(
|
||||
'*** no recipients configured so no email will be sent\n'
|
||||
'*** for %r update %s->%s\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\n' % (change.recipients,))
|
||||
'Sending notification emails to: %s' % (change.recipients,))
|
||||
extra_values = {'send_date': next(send_date)}
|
||||
|
||||
rev = change.send_single_combined_email(sha1s)
|
||||
|
@ -3527,14 +3609,14 @@ def send_emails(self, mailer, body_filter=None):
|
|||
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\n' % max_emails
|
||||
'*** Currently, multimailhook.maxCommitEmails=%d' % max_emails
|
||||
)
|
||||
return
|
||||
|
||||
for (num, sha1) in enumerate(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:\n')
|
||||
change.environment.log_msg('*** Replacing Cc: with To:')
|
||||
rev.recipients = rev.cc_recipients
|
||||
rev.cc_recipients = None
|
||||
if rev.recipients:
|
||||
|
@ -3548,7 +3630,7 @@ def send_emails(self, mailer, body_filter=None):
|
|||
if unhandled_sha1s:
|
||||
change.environment.log_error(
|
||||
'ERROR: No emails were sent for the following new commits:\n'
|
||||
' %s\n'
|
||||
' %s'
|
||||
% ('\n '.join(sorted(unhandled_sha1s)),)
|
||||
)
|
||||
|
||||
|
@ -3562,12 +3644,23 @@ def include_ref(refname, ref_filter_regex, is_inclusion_filter):
|
|||
|
||||
|
||||
def run_as_post_receive_hook(environment, mailer):
|
||||
ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(True)
|
||||
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)
|
||||
changes = []
|
||||
for line in sys.stdin:
|
||||
while True:
|
||||
line = read_line(sys.stdin)
|
||||
if line == '':
|
||||
break
|
||||
(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))
|
||||
|
||||
if not include_ref(refname, ref_filter_regex, is_inclusion_filter):
|
||||
continue
|
||||
if not include_ref(refname, send_filter_regex, send_is_inclusion_filter):
|
||||
continue
|
||||
changes.append(
|
||||
ReferenceChange.create(environment, oldrev, newrev, refname)
|
||||
)
|
||||
|
@ -3579,9 +3672,13 @@ def run_as_post_receive_hook(environment, mailer):
|
|||
|
||||
|
||||
def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False):
|
||||
ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(True)
|
||||
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)
|
||||
if not include_ref(refname, ref_filter_regex, is_inclusion_filter):
|
||||
return
|
||||
if not include_ref(refname, send_filter_regex, send_is_inclusion_filter):
|
||||
return
|
||||
changes = [
|
||||
ReferenceChange.create(
|
||||
environment,
|
||||
|
@ -3596,6 +3693,75 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=
|
|||
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)
|
||||
|
||||
def inc_exc_lusion(b):
|
||||
if b:
|
||||
return 'inclusion'
|
||||
else:
|
||||
return 'exclusion'
|
||||
|
||||
if send_filter_regex:
|
||||
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(os.linesep)
|
||||
|
||||
sys.stdout.write(
|
||||
"Refs marked as EXCLUDE are excluded by either refFilterInclusionRegex\n"
|
||||
"or refFilterExclusionRegex. No emails will be sent for commits included\n"
|
||||
"in these refs.\n"
|
||||
"Refs marked as DONT-SEND are excluded by either refFilterDoSendRegex or\n"
|
||||
"refFilterDontSendRegex, but not by either refFilterInclusionRegex or\n"
|
||||
"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")
|
||||
|
||||
sys.stdout.write(os.linesep)
|
||||
|
||||
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')
|
||||
elif not include_ref(refname, send_filter_regex, send_is_inclusion):
|
||||
sys.stdout.write(' DONT-SEND')
|
||||
else:
|
||||
sys.stdout.write(' DO-SEND')
|
||||
|
||||
sys.stdout.write(os.linesep)
|
||||
|
||||
|
||||
def show_env(environment, out):
|
||||
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')
|
||||
# Flush to avoid interleaving with further log output
|
||||
out.flush()
|
||||
|
||||
|
||||
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)
|
||||
stdin = sys.stdin.readline()
|
||||
sys.stdout.write("You have just entered:" + os.linesep)
|
||||
sys.stdout.write(stdin)
|
||||
sys.stdout.write("git-multimail seems properly set up." + os.linesep)
|
||||
|
||||
|
||||
def choose_mailer(config, environment):
|
||||
mailer = config.get('mailer', default='sendmail')
|
||||
|
||||
|
@ -3608,6 +3774,7 @@ def choose_mailer(config, environment):
|
|||
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,
|
||||
smtpserverdebuglevel=smtpserverdebuglevel,
|
||||
|
@ -3620,43 +3787,41 @@ def choose_mailer(config, environment):
|
|||
command = config.get('sendmailcommand')
|
||||
if command:
|
||||
command = shlex.split(command)
|
||||
mailer = SendMailer(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".\n'
|
||||
'please use one of "smtp" or "sendmail".'
|
||||
)
|
||||
sys.exit(1)
|
||||
return mailer
|
||||
|
||||
|
||||
KNOWN_ENVIRONMENTS = {
|
||||
'generic': GenericEnvironmentMixin,
|
||||
'gitolite': GitoliteEnvironmentMixin,
|
||||
'stash': StashEnvironmentMixin,
|
||||
'gerrit': GerritEnvironmentMixin,
|
||||
'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)
|
||||
return env
|
||||
|
||||
|
||||
def choose_environment_name(config, env, osenv):
|
||||
if not osenv:
|
||||
osenv = os.environ
|
||||
|
||||
environment_mixins = [
|
||||
ConfigRefFilterEnvironmentMixin,
|
||||
ProjectdescEnvironmentMixin,
|
||||
ConfigMaxlinesEnvironmentMixin,
|
||||
ComputeFQDNEnvironmentMixin,
|
||||
ConfigFilterLinesEnvironmentMixin,
|
||||
PusherDomainEnvironmentMixin,
|
||||
ConfigOptionsEnvironmentMixin,
|
||||
]
|
||||
environment_kw = {
|
||||
'osenv': osenv,
|
||||
'config': config,
|
||||
}
|
||||
|
||||
if not env:
|
||||
env = config.get('environment')
|
||||
|
||||
|
@ -3665,8 +3830,58 @@ def choose_environment(config, osenv=None, env=None, recipients=None,
|
|||
env = 'gitolite'
|
||||
else:
|
||||
env = 'generic'
|
||||
return env
|
||||
|
||||
environment_mixins.insert(0, KNOWN_ENVIRONMENTS[env])
|
||||
|
||||
COMMON_ENVIRONMENT_MIXINS = [
|
||||
ConfigRecipientsEnvironmentMixin,
|
||||
CLIRecipientsEnvironmentMixin,
|
||||
ConfigRefFilterEnvironmentMixin,
|
||||
ProjectdescEnvironmentMixin,
|
||||
ConfigMaxlinesEnvironmentMixin,
|
||||
ComputeFQDNEnvironmentMixin,
|
||||
ConfigFilterLinesEnvironmentMixin,
|
||||
PusherDomainEnvironmentMixin,
|
||||
ConfigOptionsEnvironmentMixin,
|
||||
]
|
||||
|
||||
|
||||
def build_environment_klass(env_name):
|
||||
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']
|
||||
environment_mixins.append(high_prec_mixin)
|
||||
environment_mixins = environment_mixins + COMMON_ENVIRONMENT_MIXINS
|
||||
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
|
||||
return environment_klass
|
||||
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
if env == 'stash':
|
||||
environment_kw['user'] = hook_info['stash_user']
|
||||
|
@ -3676,20 +3891,8 @@ def choose_environment(config, osenv=None, env=None, recipients=None,
|
|||
environment_kw['submitter'] = hook_info['submitter']
|
||||
environment_kw['update_method'] = hook_info['update_method']
|
||||
|
||||
if recipients:
|
||||
environment_mixins.insert(0, StaticRecipientsEnvironmentMixin)
|
||||
environment_kw['refchange_recipients'] = recipients
|
||||
environment_kw['announce_recipients'] = recipients
|
||||
environment_kw['revision_recipients'] = recipients
|
||||
environment_kw['scancommitforcc'] = config.get('scancommitforcc')
|
||||
else:
|
||||
environment_mixins.insert(0, ConfigRecipientsEnvironmentMixin)
|
||||
environment_kw['cli_recipients'] = recipients
|
||||
|
||||
environment_klass = type(
|
||||
'EffectiveEnvironment',
|
||||
tuple(environment_mixins) + (Environment,),
|
||||
{},
|
||||
)
|
||||
return environment_klass(**environment_kw)
|
||||
|
||||
|
||||
|
@ -3710,7 +3913,8 @@ def get_version():
|
|||
return __version__
|
||||
|
||||
|
||||
def compute_gerrit_options(options, args, required_gerrit_options):
|
||||
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.")
|
||||
|
@ -3727,24 +3931,11 @@ def compute_gerrit_options(options, args, required_gerrit_options):
|
|||
# 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, options.refname)) and
|
||||
if (not os.path.exists(os.path.join(git_dir, raw_refname)) and
|
||||
os.path.exists(os.path.join(git_dir, 'refs', 'heads',
|
||||
options.refname))):
|
||||
raw_refname))):
|
||||
options.refname = 'refs/heads/' + options.refname
|
||||
|
||||
# Convert each string option unicode for Python3.
|
||||
if PYTHON3:
|
||||
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')
|
||||
setattr(options, opt, dec)
|
||||
|
||||
# New revisions can appear in a gerrit repository either due to someone
|
||||
# pushing directly (in which case options.submitter will be set), or they
|
||||
# can press "Submit this patchset" in the web UI for some CR (in which
|
||||
|
@ -3784,6 +3975,20 @@ def compute_gerrit_options(options, args, required_gerrit_options):
|
|||
|
||||
|
||||
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']
|
||||
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')
|
||||
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 "
|
||||
|
@ -3797,12 +4002,78 @@ def check_hook_specific_args(options, args):
|
|||
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)
|
||||
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, {}
|
||||
|
||||
|
||||
class Logger(object):
|
||||
def parse_verbose(self, verbose):
|
||||
if verbose > 0:
|
||||
return logging.DEBUG
|
||||
else:
|
||||
return logging.INFO
|
||||
|
||||
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")
|
||||
file_handler.setFormatter(log_fmt)
|
||||
log_file.addHandler(file_handler)
|
||||
log_file.setLevel(verbosity)
|
||||
return log_file
|
||||
|
||||
def __init__(self, environment):
|
||||
self.environment = environment
|
||||
self.loggers = []
|
||||
stderr_log = logging.getLogger('git_multimail.stderr')
|
||||
|
||||
class EncodedStderr(object):
|
||||
def write(self, x):
|
||||
write_str(sys.stderr, x)
|
||||
|
||||
def flush(self):
|
||||
sys.stderr.flush()
|
||||
|
||||
stderr_handler = logging.StreamHandler(EncodedStderr())
|
||||
stderr_log.addHandler(stderr_handler)
|
||||
stderr_log.setLevel(self.parse_verbose(environment.verbose))
|
||||
self.loggers.append(stderr_log)
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
self.loggers.append(error_log_file)
|
||||
|
||||
def info(self, msg):
|
||||
for l in self.loggers:
|
||||
l.info(msg)
|
||||
|
||||
def debug(self, msg):
|
||||
for l in self.loggers:
|
||||
l.debug(msg)
|
||||
|
||||
def warning(self, msg):
|
||||
for l in self.loggers:
|
||||
l.warning(msg)
|
||||
|
||||
def error(self, msg):
|
||||
for l in self.loggers:
|
||||
l.error(msg)
|
||||
|
||||
|
||||
def main(args):
|
||||
parser = optparse.OptionParser(
|
||||
description=__doc__,
|
||||
|
@ -3829,7 +4100,7 @@ def main(args):
|
|||
'--show-env', action='store_true', default=False,
|
||||
help=(
|
||||
'Write to stderr the values determined for the environment '
|
||||
'(intended for debugging purposes).'
|
||||
'(intended for debugging purposes), then proceed normally.'
|
||||
),
|
||||
)
|
||||
parser.add_option(
|
||||
|
@ -3854,6 +4125,22 @@ def main(args):
|
|||
"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"
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_option(
|
||||
'--check-ref-filter', action='store_true', default=False,
|
||||
help=(
|
||||
'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.
|
||||
# code.google.com/p/gerrit/source/browse/Documentation/config-hooks.txt
|
||||
|
@ -3880,11 +4167,16 @@ def main(args):
|
|||
sys.stdout.write('git-multimail version ' + get_version() + '\n')
|
||||
return
|
||||
|
||||
if options.python_version:
|
||||
sys.stdout.write('Python version ' + sys.version + '\n')
|
||||
return
|
||||
|
||||
if options.c:
|
||||
Config.add_config_parameters(options.c)
|
||||
|
||||
config = Config('multimailhook')
|
||||
|
||||
environment = None
|
||||
try:
|
||||
environment = choose_environment(
|
||||
config, osenv=os.environ,
|
||||
|
@ -3894,38 +4186,52 @@ def main(args):
|
|||
)
|
||||
|
||||
if options.show_env:
|
||||
sys.stderr.write('Environment values:\n')
|
||||
for (k, v) in sorted(environment.get_values().items()):
|
||||
sys.stderr.write(' %s : %r\n' % (k, v))
|
||||
sys.stderr.write('\n')
|
||||
show_env(environment, sys.stderr)
|
||||
|
||||
if options.stdout or environment.stdout:
|
||||
mailer = OutputMailer(sys.stdout)
|
||||
else:
|
||||
mailer = choose_mailer(config, environment)
|
||||
|
||||
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)
|
||||
elif must_check_setup:
|
||||
check_setup(environment)
|
||||
# Dual mode: if arguments were specified on the command line, run
|
||||
# like an update hook; otherwise, run as a post-receive hook.
|
||||
if args:
|
||||
elif args:
|
||||
if len(args) != 3:
|
||||
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)
|
||||
else:
|
||||
run_as_post_receive_hook(environment, mailer)
|
||||
except ConfigurationException:
|
||||
sys.exit(sys.exc_info()[1])
|
||||
except SystemExit:
|
||||
raise
|
||||
except Exception:
|
||||
t, e, tb = sys.exc_info()
|
||||
import traceback
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.write('Exception \'' + t.__name__ +
|
||||
'\' raised. Please report this as a bug to\n')
|
||||
sys.stdout.write('https://github.com/git-multimail/git-multimail/issues\n')
|
||||
sys.stdout.write('with the information below:\n\n')
|
||||
sys.stdout.write('git-multimail version ' + get_version() + '\n')
|
||||
sys.stdout.write('Python version ' + sys.version + '\n')
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
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())
|
||||
try:
|
||||
environment.get_logger().error(msg)
|
||||
except:
|
||||
sys.stderr.write(msg)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
Loading…
Reference in New Issue