mirror of
https://github.com/git/git
synced 2024-08-28 12:09:19 +00:00
git-multimail: update to release 1.5.0
Changes are described in CHANGES. Contributions-by: Matthieu Moy <git@matthieu-moy.fr> Contributions-by: William Stewart <william.stewart@booking.com> Contributions-by: Ville Skyttä <ville.skytta@iki.fi> Contributions-by: Dirk Olmes <dirk.olmes@codedo.de> Contributions-by: Björn Kautler <Bjoern@Kautler.net> Contributions-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org> Contributions-by: Gareth Pye <garethp@gpsatsys.com.au> Contributions-by: David Lazar <lazard@csail.mit.edu> Signed-off-by: Matthieu Moy <git@matthieu-moy.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
ecbdaf0899
commit
99177b34db
|
@ -1,3 +1,59 @@
|
||||||
|
Release 1.5.0
|
||||||
|
=============
|
||||||
|
|
||||||
|
Backward-incompatible change
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
The name of classes for environment was misnamed as `*Environement`.
|
||||||
|
It is now `*Environment`.
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* A Thread-Index header is now added to each email sent (except for
|
||||||
|
combined emails where it would not make sense), so that MS Outlook
|
||||||
|
properly groups messages by threads even though they have a
|
||||||
|
different subject line. Unfortunately, even adding this header the
|
||||||
|
threading still seems to be unreliable, but it is unclear whether
|
||||||
|
this is an issue on our side or on MS Outlook's side (see discussion
|
||||||
|
here: https://github.com/git-multimail/git-multimail/pull/194).
|
||||||
|
|
||||||
|
* A new variable multimailhook.ExcludeMergeRevisions was added to send
|
||||||
|
notification emails only for non-merge commits.
|
||||||
|
|
||||||
|
* For gitolite environment, it is now possible to specify the mail map
|
||||||
|
in a separate file in addition to gitolite.conf, using the variable
|
||||||
|
multimailhook.MailaddressMap.
|
||||||
|
|
||||||
|
Internal changes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* The testsuite now uses GIT_PRINT_SHA1_ELLIPSIS where needed for
|
||||||
|
compatibility with recent Git versions. Only tests are affected.
|
||||||
|
|
||||||
|
* We don't try to install pyflakes in the continuous integration job
|
||||||
|
for old Python versions where it's no longer available.
|
||||||
|
|
||||||
|
* Stop using the deprecated cgi.escape in Python 3.
|
||||||
|
|
||||||
|
* New flake8 warnings have been fixed.
|
||||||
|
|
||||||
|
* Python 3.6 is now tested against on Travis-CI.
|
||||||
|
|
||||||
|
* A bunch of lgtm.com warnings have been fixed.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* SMTPMailer logs in only once now. It used to re-login for each email
|
||||||
|
sent which triggered errors for some SMTP servers.
|
||||||
|
|
||||||
|
* migrate-mailhook-config was broken by internal refactoring, it
|
||||||
|
should now work again.
|
||||||
|
|
||||||
|
This version was tested with Python 2.6 to 3.7. It was tested with Git
|
||||||
|
1.7.10.406.gdc801, 2.15.1 and 2.20.1.98.gecbdaf0.
|
||||||
|
|
||||||
Release 1.4.0
|
Release 1.4.0
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,8 @@ Contributing
|
||||||
git-multimail is an open-source project, built by volunteers. We would
|
git-multimail is an open-source project, built by volunteers. We would
|
||||||
welcome your help!
|
welcome your help!
|
||||||
|
|
||||||
The current maintainers are Matthieu Moy
|
The current maintainers are `Matthieu Moy <http://matthieu-moy.fr>`__ and
|
||||||
<matthieu.moy@grenoble-inp.fr> and Michael Haggerty
|
`Michael Haggerty <https://github.com/mhagger>`__.
|
||||||
<mhagger@alum.mit.edu>.
|
|
||||||
|
|
||||||
Please note that although a copy of git-multimail is distributed in
|
Please note that although a copy of git-multimail is distributed in
|
||||||
the "contrib" section of the main Git project, development takes place
|
the "contrib" section of the main Git project, development takes place
|
||||||
|
@ -33,6 +32,29 @@ mailing list`_.
|
||||||
Please CC emails regarding git-multimail to the maintainers so that we
|
Please CC emails regarding git-multimail to the maintainers so that we
|
||||||
don't overlook them.
|
don't overlook them.
|
||||||
|
|
||||||
|
Help needed: testers/maintainer for specific environments/OS
|
||||||
|
------------------------------------------------------------
|
||||||
|
|
||||||
|
The current maintainer uses and tests git-multimail on Linux with the
|
||||||
|
Generic environment. More testers, or better contributors are needed
|
||||||
|
to test git-multimail on other real-life setups:
|
||||||
|
|
||||||
|
* Mac OS X, Windows: git-multimail is currently not supported on these
|
||||||
|
platforms. But since we have no external dependencies and try to
|
||||||
|
write code as portable as possible, it is possible that
|
||||||
|
git-multimail already runs there and if not, it is likely that it
|
||||||
|
could be ported easily.
|
||||||
|
|
||||||
|
Patches to improve support for Windows and OS X are welcome.
|
||||||
|
Ideally, there would be a sub-maintainer for each OS who would test
|
||||||
|
at least once before each release (around twice a year).
|
||||||
|
|
||||||
|
* Gerrit, Stash, Gitolite environments: although the testsuite
|
||||||
|
contains tests for these environments, a tester/maintainer for each
|
||||||
|
environment would be welcome to test and report failure (or success)
|
||||||
|
on real-life environments periodically (here also, feedback before
|
||||||
|
each release would be highly appreciated).
|
||||||
|
|
||||||
|
|
||||||
.. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail
|
.. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail
|
||||||
.. _`Git mailing list`: git@vger.kernel.org
|
.. _`Git mailing list`: git@vger.kernel.org
|
||||||
|
|
|
@ -6,10 +6,10 @@ website:
|
||||||
https://github.com/git-multimail/git-multimail
|
https://github.com/git-multimail/git-multimail
|
||||||
|
|
||||||
The version in this directory was obtained from the upstream project
|
The version in this directory was obtained from the upstream project
|
||||||
on August 17 2016 and consists of the "git-multimail" subdirectory from
|
on January 07 2019 and consists of the "git-multimail" subdirectory from
|
||||||
revision
|
revision
|
||||||
|
|
||||||
07b1cb6bfd7be156c62e1afa17cae13b850a869f refs/tags/1.4.0
|
04e80e6c40be465cc62b6c246f0fcb8fd2cfd454 refs/tags/1.5.0
|
||||||
|
|
||||||
Please see the README file in this directory for information about how
|
Please see the README file in this directory for information about how
|
||||||
to report bugs or contribute to git-multimail.
|
to report bugs or contribute to git-multimail.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
git-multimail version 1.4.0
|
git-multimail version 1.5.0
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
|
.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
|
||||||
|
@ -20,8 +20,8 @@ GPLv2 (see the COPYING file for details).
|
||||||
|
|
||||||
Please note: although, as a convenience, git-multimail may be
|
Please note: although, as a convenience, git-multimail may be
|
||||||
distributed along with the main Git project, development of
|
distributed along with the main Git project, development of
|
||||||
git-multimail takes place in its own, separate project. See section
|
git-multimail takes place in its own, separate project. Please, read
|
||||||
"Getting involved" below for more information.
|
`<CONTRIBUTING.rst>`__ for more information.
|
||||||
|
|
||||||
|
|
||||||
By default, for each push received by the repository, git-multimail:
|
By default, for each push received by the repository, git-multimail:
|
||||||
|
@ -89,6 +89,10 @@ Requirements
|
||||||
the multimailhook.mailer configuration variable below for how to
|
the multimailhook.mailer configuration variable below for how to
|
||||||
configure git-multimail to send emails via an SMTP server.
|
configure git-multimail to send emails via an SMTP server.
|
||||||
|
|
||||||
|
* git-multimail is currently tested only on Linux. It may or may not
|
||||||
|
work on other platforms such as Windows and Mac OS. See
|
||||||
|
`<CONTRIBUTING.rst>`__ to improve the situation.
|
||||||
|
|
||||||
|
|
||||||
Invocation
|
Invocation
|
||||||
----------
|
----------
|
||||||
|
@ -369,7 +373,7 @@ multimailhook.mailer
|
||||||
unset, then the value of multimailhook.from is used.
|
unset, then the value of multimailhook.from is used.
|
||||||
|
|
||||||
multimailhook.smtpServerTimeout
|
multimailhook.smtpServerTimeout
|
||||||
Timeout in seconds.
|
Timeout in seconds. Default is 10.
|
||||||
|
|
||||||
multimailhook.smtpEncryption
|
multimailhook.smtpEncryption
|
||||||
Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls).
|
Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls).
|
||||||
|
@ -419,8 +423,20 @@ multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
|
||||||
If config values are unset, the value of the From: header is
|
If config values are unset, the value of the From: header is
|
||||||
determined as follows:
|
determined as follows:
|
||||||
|
|
||||||
1. (gitolite environment only) Parse gitolite.conf, looking for a
|
1. (gitolite environment only)
|
||||||
block of comments that looks like this::
|
1.a) If ``multimailhook.MailaddressMap`` is set, and is a path
|
||||||
|
to an existing file (if relative, it is considered relative to
|
||||||
|
the place where ``gitolite.conf`` is located), then this file
|
||||||
|
should contain lines like::
|
||||||
|
|
||||||
|
username Firstname Lastname <email@example.com>
|
||||||
|
|
||||||
|
git-multimail will then look for a line where ``$GL_USER``
|
||||||
|
matches the ``username`` part, and use the rest of the line for
|
||||||
|
the ``From:`` header.
|
||||||
|
|
||||||
|
1.b) Parse gitolite.conf, looking for a block of comments that
|
||||||
|
looks like this::
|
||||||
|
|
||||||
# BEGIN USER EMAILS
|
# BEGIN USER EMAILS
|
||||||
# username Firstname Lastname <email@example.com>
|
# username Firstname Lastname <email@example.com>
|
||||||
|
@ -436,6 +452,11 @@ multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
|
||||||
|
|
||||||
3. Use the value of multimailhook.envelopeSender.
|
3. Use the value of multimailhook.envelopeSender.
|
||||||
|
|
||||||
|
multimailhook.MailaddressMap
|
||||||
|
(gitolite environment only)
|
||||||
|
File to look for a ``From:`` address based on the user doing the
|
||||||
|
push. Defaults to unset. See ``multimailhook.from`` for details.
|
||||||
|
|
||||||
multimailhook.administrator
|
multimailhook.administrator
|
||||||
The name and/or email address of the administrator of the Git
|
The name and/or email address of the administrator of the Git
|
||||||
repository; used in FOOTER_TEMPLATE. Default is
|
repository; used in FOOTER_TEMPLATE. Default is
|
||||||
|
@ -484,6 +505,11 @@ multimailhook.maxCommitEmails
|
||||||
mailbombing, for example on an initial push. To disable commit
|
mailbombing, for example on an initial push. To disable commit
|
||||||
emails limit, set this option to 0. The default is 500.
|
emails limit, set this option to 0. The default is 500.
|
||||||
|
|
||||||
|
multimailhook.excludeMergeRevisions
|
||||||
|
When sending out revision emails, do not consider merge commits (the
|
||||||
|
functional equivalent of `rev-list --no-merges`).
|
||||||
|
The default is `false` (send merge commit emails).
|
||||||
|
|
||||||
multimailhook.emailStrictUTF8
|
multimailhook.emailStrictUTF8
|
||||||
If this boolean option is set to `true`, then the main part of the
|
If this boolean option is set to `true`, then the main part of the
|
||||||
email body is forced to be valid UTF-8. Any characters that are
|
email body is forced to be valid UTF-8. Any characters that are
|
|
@ -46,6 +46,15 @@ and add::
|
||||||
config multimailhook.mailingList = # Where emails should be sent
|
config multimailhook.mailingList = # Where emails should be sent
|
||||||
config multimailhook.from = # From address to use
|
config multimailhook.from = # From address to use
|
||||||
|
|
||||||
|
Note that by default, gitolite forbids ``<`` and ``>`` in variable
|
||||||
|
values (for security/paranoia reasons, see
|
||||||
|
`compensating for UNSAFE_PATT
|
||||||
|
<http://gitolite.com/gitolite/git-config/index.html#compensating-for-unsafe95patt>`__
|
||||||
|
in gitolite's documentation for explanations and a way to disable
|
||||||
|
this). As a consequence, you will not be able to use ``First Last
|
||||||
|
<First.Last@example.com>`` as recipient email, but specifying
|
||||||
|
``First.Last@example.com`` alone works.
|
||||||
|
|
||||||
Obviously, you can customize all parameters on a per-repository basis by
|
Obviously, you can customize all parameters on a per-repository basis by
|
||||||
adding these ``config multimailhook.*`` lines in the section
|
adding these ``config multimailhook.*`` lines in the section
|
||||||
corresponding to a repository or set of repositories.
|
corresponding to a repository or set of repositories.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#! /usr/bin/env python
|
#! /usr/bin/env python
|
||||||
|
|
||||||
__version__ = '1.4.0'
|
__version__ = '1.5.0'
|
||||||
|
|
||||||
# Copyright (c) 2015-2016 Matthieu Moy and others
|
# Copyright (c) 2015-2016 Matthieu Moy and others
|
||||||
# Copyright (c) 2012-2014 Michael Haggerty and others
|
# Copyright (c) 2012-2014 Michael Haggerty and others
|
||||||
|
@ -64,7 +64,9 @@
|
||||||
# Python < 2.6 do not have ssl, but that's OK if we don't use it.
|
# Python < 2.6 do not have ssl, but that's OK if we don't use it.
|
||||||
pass
|
pass
|
||||||
import time
|
import time
|
||||||
import cgi
|
|
||||||
|
import uuid
|
||||||
|
import base64
|
||||||
|
|
||||||
PYTHON3 = sys.version_info >= (3, 0)
|
PYTHON3 = sys.version_info >= (3, 0)
|
||||||
|
|
||||||
|
@ -73,7 +75,7 @@ def all(iterable):
|
||||||
for element in iterable:
|
for element in iterable:
|
||||||
if not element:
|
if not element:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def is_ascii(s):
|
def is_ascii(s):
|
||||||
|
@ -108,6 +110,12 @@ def read_line(f):
|
||||||
return out.decode(sys.getdefaultencoding())
|
return out.decode(sys.getdefaultencoding())
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
return out.decode(ENCODING)
|
return out.decode(ENCODING)
|
||||||
|
|
||||||
|
import html
|
||||||
|
|
||||||
|
def html_escape(s):
|
||||||
|
return html.escape(s)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
def is_string(s):
|
def is_string(s):
|
||||||
try:
|
try:
|
||||||
|
@ -130,6 +138,10 @@ def read_line(f):
|
||||||
def next(it):
|
def next(it):
|
||||||
return it.next()
|
return it.next()
|
||||||
|
|
||||||
|
import cgi
|
||||||
|
|
||||||
|
def html_escape(s):
|
||||||
|
return cgi.escape(s, True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from email.charset import Charset
|
from email.charset import Charset
|
||||||
|
@ -190,6 +202,7 @@ def next(it):
|
||||||
Message-ID: %(msgid)s
|
Message-ID: %(msgid)s
|
||||||
From: %(fromaddr)s
|
From: %(fromaddr)s
|
||||||
Reply-To: %(reply_to)s
|
Reply-To: %(reply_to)s
|
||||||
|
Thread-Index: %(thread_index)s
|
||||||
X-Git-Host: %(fqdn)s
|
X-Git-Host: %(fqdn)s
|
||||||
X-Git-Repo: %(repo_shortname)s
|
X-Git-Repo: %(repo_shortname)s
|
||||||
X-Git-Refname: %(refname)s
|
X-Git-Refname: %(refname)s
|
||||||
|
@ -322,6 +335,7 @@ def next(it):
|
||||||
Reply-To: %(reply_to)s
|
Reply-To: %(reply_to)s
|
||||||
In-Reply-To: %(reply_to_msgid)s
|
In-Reply-To: %(reply_to_msgid)s
|
||||||
References: %(reply_to_msgid)s
|
References: %(reply_to_msgid)s
|
||||||
|
Thread-Index: %(thread_index)s
|
||||||
X-Git-Host: %(fqdn)s
|
X-Git-Host: %(fqdn)s
|
||||||
X-Git-Repo: %(repo_shortname)s
|
X-Git-Repo: %(repo_shortname)s
|
||||||
X-Git-Refname: %(refname)s
|
X-Git-Refname: %(refname)s
|
||||||
|
@ -763,6 +777,9 @@ def get_summary(self):
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return isinstance(other, GitObject) and self.sha1 == other.sha1
|
return isinstance(other, GitObject) and self.sha1 == other.sha1
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.sha1)
|
return hash(self.sha1)
|
||||||
|
|
||||||
|
@ -852,7 +869,7 @@ def expand_lines(self, template, html_escape_val=False, **extra_values):
|
||||||
if html_escape_val:
|
if html_escape_val:
|
||||||
for k in values:
|
for k in values:
|
||||||
if is_string(values[k]):
|
if is_string(values[k]):
|
||||||
values[k] = cgi.escape(values[k], True)
|
values[k] = html_escape(values[k])
|
||||||
for line in template.splitlines(True):
|
for line in template.splitlines(True):
|
||||||
yield line % values
|
yield line % values
|
||||||
|
|
||||||
|
@ -909,7 +926,7 @@ def generate_email_intro(self, html_escape_val=False):
|
||||||
|
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def generate_email_body(self):
|
def generate_email_body(self, push):
|
||||||
"""Generate the main part of the email body, a line at a time.
|
"""Generate the main part of the email body, a line at a time.
|
||||||
|
|
||||||
The text in the body might be truncated after a specified
|
The text in the body might be truncated after a specified
|
||||||
|
@ -936,7 +953,7 @@ def _wrap_for_html(self, lines):
|
||||||
yield "<pre style='margin:0'>\n"
|
yield "<pre style='margin:0'>\n"
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
yield cgi.escape(line)
|
yield html_escape(line)
|
||||||
|
|
||||||
yield '</pre>\n'
|
yield '</pre>\n'
|
||||||
else:
|
else:
|
||||||
|
@ -1011,7 +1028,7 @@ def generate_email(self, push, body_filter=None, extra_header_values={}):
|
||||||
fgcolor = '404040'
|
fgcolor = '404040'
|
||||||
|
|
||||||
# Chop the trailing LF, we don't want it inside <pre>.
|
# Chop the trailing LF, we don't want it inside <pre>.
|
||||||
line = cgi.escape(line[:-1])
|
line = html_escape(line[:-1])
|
||||||
|
|
||||||
if bgcolor or fgcolor:
|
if bgcolor or fgcolor:
|
||||||
style = 'display:block; white-space:pre;'
|
style = 'display:block; white-space:pre;'
|
||||||
|
@ -1060,6 +1077,10 @@ def __init__(self, reference_change, rev, num, 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.recipients = self.environment.get_revision_recipients(self)
|
||||||
|
|
||||||
|
# -s is short for --no-patch, but -s works on older git's (e.g. 1.7)
|
||||||
|
self.parents = read_git_lines(['show', '-s', '--format=%P',
|
||||||
|
self.rev.sha1])[0].split()
|
||||||
|
|
||||||
self.cc_recipients = ''
|
self.cc_recipients = ''
|
||||||
if self.environment.get_scancommitforcc():
|
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())
|
||||||
|
@ -1090,6 +1111,7 @@ def _compute_values(self):
|
||||||
oneline = oneline[:max_subject_length - 6] + ' [...]'
|
oneline = oneline[:max_subject_length - 6] + ' [...]'
|
||||||
|
|
||||||
values['rev'] = self.rev.sha1
|
values['rev'] = self.rev.sha1
|
||||||
|
values['parents'] = ' '.join(self.parents)
|
||||||
values['rev_short'] = self.rev.short
|
values['rev_short'] = self.rev.short
|
||||||
values['change_type'] = self.change_type
|
values['change_type'] = self.change_type
|
||||||
values['refname'] = self.refname
|
values['refname'] = self.refname
|
||||||
|
@ -1097,6 +1119,7 @@ def _compute_values(self):
|
||||||
values['short_refname'] = self.reference_change.short_refname
|
values['short_refname'] = self.reference_change.short_refname
|
||||||
values['refname_type'] = self.reference_change.refname_type
|
values['refname_type'] = self.reference_change.refname_type
|
||||||
values['reply_to_msgid'] = self.reference_change.msgid
|
values['reply_to_msgid'] = self.reference_change.msgid
|
||||||
|
values['thread_index'] = self.reference_change.thread_index
|
||||||
values['num'] = self.num
|
values['num'] = self.num
|
||||||
values['tot'] = self.tot
|
values['tot'] = self.tot
|
||||||
values['recipients'] = self.recipients
|
values['recipients'] = self.recipients
|
||||||
|
@ -1244,6 +1267,23 @@ def create(environment, oldrev, newrev, refname):
|
||||||
old=old, new=new, rev=rev,
|
old=old, new=new, rev=rev,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_thread_index():
|
||||||
|
"""Return a string appropriate for the Thread-Index header,
|
||||||
|
needed by MS Outlook to get threading right.
|
||||||
|
|
||||||
|
The format is (base64-encoded):
|
||||||
|
- 1 byte must be 1
|
||||||
|
- 5 bytes encode a date (hardcoded here)
|
||||||
|
- 16 bytes for a globally unique identifier
|
||||||
|
|
||||||
|
FIXME: Unfortunately, even with the Thread-Index field, MS
|
||||||
|
Outlook doesn't seem to do the threading reliably (see
|
||||||
|
https://github.com/git-multimail/git-multimail/pull/194).
|
||||||
|
"""
|
||||||
|
thread_index = b'\x01\x00\x00\x12\x34\x56' + uuid.uuid4().bytes
|
||||||
|
return base64.standard_b64encode(thread_index).decode('ascii')
|
||||||
|
|
||||||
def __init__(self, environment, refname, short_refname, old, new, rev):
|
def __init__(self, environment, refname, short_refname, old, new, rev):
|
||||||
Change.__init__(self, environment)
|
Change.__init__(self, environment)
|
||||||
self.change_type = {
|
self.change_type = {
|
||||||
|
@ -1257,6 +1297,7 @@ def __init__(self, environment, refname, short_refname, old, new, rev):
|
||||||
self.new = new
|
self.new = new
|
||||||
self.rev = rev
|
self.rev = rev
|
||||||
self.msgid = make_msgid()
|
self.msgid = make_msgid()
|
||||||
|
self.thread_index = self.make_thread_index()
|
||||||
self.diffopts = environment.diffopts
|
self.diffopts = environment.diffopts
|
||||||
self.graphopts = environment.graphopts
|
self.graphopts = environment.graphopts
|
||||||
self.logopts = environment.logopts
|
self.logopts = environment.logopts
|
||||||
|
@ -1276,6 +1317,7 @@ def _compute_values(self):
|
||||||
values['refname'] = self.refname
|
values['refname'] = self.refname
|
||||||
values['short_refname'] = self.short_refname
|
values['short_refname'] = self.short_refname
|
||||||
values['msgid'] = self.msgid
|
values['msgid'] = self.msgid
|
||||||
|
values['thread_index'] = self.thread_index
|
||||||
values['recipients'] = self.recipients
|
values['recipients'] = self.recipients
|
||||||
values['oldrev'] = str(self.old)
|
values['oldrev'] = str(self.old)
|
||||||
values['oldrev_short'] = self.old.short
|
values['oldrev_short'] = self.old.short
|
||||||
|
@ -1941,6 +1983,9 @@ class Mailer(object):
|
||||||
def __init__(self, environment):
|
def __init__(self, environment):
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def send(self, lines, to_addrs):
|
def send(self, lines, to_addrs):
|
||||||
"""Send an email consisting of lines.
|
"""Send an email consisting of lines.
|
||||||
|
|
||||||
|
@ -2054,6 +2099,7 @@ def __init__(self, environment,
|
||||||
self.username = smtpuser
|
self.username = smtpuser
|
||||||
self.password = smtppass
|
self.password = smtppass
|
||||||
self.smtpcacerts = smtpcacerts
|
self.smtpcacerts = smtpcacerts
|
||||||
|
self.loggedin = False
|
||||||
try:
|
try:
|
||||||
def call(klass, server, timeout):
|
def call(klass, server, timeout):
|
||||||
try:
|
try:
|
||||||
|
@ -2130,20 +2176,30 @@ def call(klass, server, timeout):
|
||||||
% (self.smtpserver, sys.exc_info()[1]))
|
% (self.smtpserver, sys.exc_info()[1]))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def __del__(self):
|
def close(self):
|
||||||
if hasattr(self, 'smtp'):
|
if hasattr(self, 'smtp'):
|
||||||
self.smtp.quit()
|
self.smtp.quit()
|
||||||
del self.smtp
|
del self.smtp
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
def send(self, lines, to_addrs):
|
def send(self, lines, to_addrs):
|
||||||
try:
|
try:
|
||||||
if self.username or self.password:
|
if self.username or self.password:
|
||||||
self.smtp.login(self.username, self.password)
|
if not self.loggedin:
|
||||||
|
self.smtp.login(self.username, self.password)
|
||||||
|
self.loggedin = True
|
||||||
msg = ''.join(lines)
|
msg = ''.join(lines)
|
||||||
# turn comma-separated list into Python list if needed.
|
# turn comma-separated list into Python list if needed.
|
||||||
if is_string(to_addrs):
|
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)
|
self.smtp.sendmail(self.envelopesender, to_addrs, msg)
|
||||||
|
except socket.timeout:
|
||||||
|
self.environment.get_logger().error(
|
||||||
|
'*** Error sending email ***\n'
|
||||||
|
'*** SMTP server timed out (timeout is %s)\n'
|
||||||
|
% self.smtpservertimeout)
|
||||||
except smtplib.SMTPResponseException:
|
except smtplib.SMTPResponseException:
|
||||||
err = sys.exc_info()[1]
|
err = sys.exc_info()[1]
|
||||||
self.environment.get_logger().error(
|
self.environment.get_logger().error(
|
||||||
|
@ -2171,7 +2227,8 @@ class OutputMailer(Mailer):
|
||||||
|
|
||||||
SEPARATOR = '=' * 75 + '\n'
|
SEPARATOR = '=' * 75 + '\n'
|
||||||
|
|
||||||
def __init__(self, f):
|
def __init__(self, f, environment=None):
|
||||||
|
super(OutputMailer, self).__init__(environment=environment)
|
||||||
self.f = f
|
self.f = f
|
||||||
|
|
||||||
def send(self, lines, to_addrs):
|
def send(self, lines, to_addrs):
|
||||||
|
@ -2382,6 +2439,7 @@ def __init__(self, osenv=None):
|
||||||
self.html_in_footer = False
|
self.html_in_footer = False
|
||||||
self.commitBrowseURL = None
|
self.commitBrowseURL = None
|
||||||
self.maxcommitemails = 500
|
self.maxcommitemails = 500
|
||||||
|
self.excludemergerevisions = False
|
||||||
self.diffopts = ['--stat', '--summary', '--find-copies-harder']
|
self.diffopts = ['--stat', '--summary', '--find-copies-harder']
|
||||||
self.graphopts = ['--oneline', '--decorate']
|
self.graphopts = ['--oneline', '--decorate']
|
||||||
self.logopts = []
|
self.logopts = []
|
||||||
|
@ -2621,6 +2679,8 @@ def __init__(self, config, **kw):
|
||||||
|
|
||||||
self.commitBrowseURL = config.get('commitBrowseURL')
|
self.commitBrowseURL = config.get('commitBrowseURL')
|
||||||
|
|
||||||
|
self.excludemergerevisions = config.get('excludeMergeRevisions')
|
||||||
|
|
||||||
maxcommitemails = config.get('maxcommitemails')
|
maxcommitemails = config.get('maxcommitemails')
|
||||||
if maxcommitemails is not None:
|
if maxcommitemails is not None:
|
||||||
try:
|
try:
|
||||||
|
@ -3152,7 +3212,10 @@ def get_pusher(self):
|
||||||
return self.osenv.get('GL_USER', 'unknown user')
|
return self.osenv.get('GL_USER', 'unknown user')
|
||||||
|
|
||||||
|
|
||||||
class GitoliteEnvironmentLowPrecMixin(Environment):
|
class GitoliteEnvironmentLowPrecMixin(
|
||||||
|
ConfigEnvironmentMixin,
|
||||||
|
Environment):
|
||||||
|
|
||||||
def get_repo_shortname(self):
|
def get_repo_shortname(self):
|
||||||
# The gitolite environment variable $GL_REPO is a pretty good
|
# The gitolite environment variable $GL_REPO is a pretty good
|
||||||
# repo_shortname (though it's probably not as good as a value
|
# repo_shortname (though it's probably not as good as a value
|
||||||
|
@ -3162,6 +3225,16 @@ def get_repo_shortname(self):
|
||||||
super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname()
|
super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _compile_regex(re_template):
|
||||||
|
return (
|
||||||
|
re.compile(re_template % x)
|
||||||
|
for x in (
|
||||||
|
r'BEGIN\s+USER\s+EMAILS',
|
||||||
|
r'([^\s]+)\s+(.*)',
|
||||||
|
r'END\s+USER\s+EMAILS',
|
||||||
|
))
|
||||||
|
|
||||||
def get_fromaddr(self, change=None):
|
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:
|
if GL_USER is not None:
|
||||||
|
@ -3174,18 +3247,42 @@ def get_fromaddr(self, change=None):
|
||||||
GL_CONF = self.osenv.get(
|
GL_CONF = self.osenv.get(
|
||||||
'GL_CONF',
|
'GL_CONF',
|
||||||
os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf'))
|
os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf'))
|
||||||
|
|
||||||
|
mailaddress_map = self.config.get('MailaddressMap')
|
||||||
|
# If relative, consider relative to GL_CONF:
|
||||||
|
if mailaddress_map:
|
||||||
|
mailaddress_map = os.path.join(os.path.dirname(GL_CONF),
|
||||||
|
mailaddress_map)
|
||||||
|
if os.path.isfile(mailaddress_map):
|
||||||
|
f = open(mailaddress_map, 'rU')
|
||||||
|
try:
|
||||||
|
# Leading '#' is optional
|
||||||
|
re_begin, re_user, re_end = self._compile_regex(
|
||||||
|
r'^(?:\s*#)?\s*%s\s*$')
|
||||||
|
for l in f:
|
||||||
|
l = l.rstrip('\n')
|
||||||
|
if re_begin.match(l) or re_end.match(l):
|
||||||
|
continue # Ignore these lines
|
||||||
|
m = re_user.match(l)
|
||||||
|
if m:
|
||||||
|
if m.group(1) == GL_USER:
|
||||||
|
return m.group(2)
|
||||||
|
else:
|
||||||
|
continue # Not this user, but not an error
|
||||||
|
raise ConfigurationException(
|
||||||
|
"Syntax error in mail address map.\n"
|
||||||
|
"Check file {}.\n"
|
||||||
|
"Line: {}".format(mailaddress_map, l))
|
||||||
|
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
if os.path.isfile(GL_CONF):
|
if os.path.isfile(GL_CONF):
|
||||||
f = open(GL_CONF, 'rU')
|
f = open(GL_CONF, 'rU')
|
||||||
try:
|
try:
|
||||||
in_user_emails_section = False
|
in_user_emails_section = False
|
||||||
re_template = r'^\s*#\s*%s\s*$'
|
re_begin, re_user, re_end = self._compile_regex(
|
||||||
re_begin, re_user, re_end = (
|
r'^\s*#\s*%s\s*$')
|
||||||
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',
|
|
||||||
))
|
|
||||||
for l in f:
|
for l in f:
|
||||||
l = l.rstrip('\n')
|
l = l.rstrip('\n')
|
||||||
if not in_user_emails_section:
|
if not in_user_emails_section:
|
||||||
|
@ -3195,8 +3292,8 @@ def get_fromaddr(self, change=None):
|
||||||
if re_end.match(l):
|
if re_end.match(l):
|
||||||
break
|
break
|
||||||
m = re_user.match(l)
|
m = re_user.match(l)
|
||||||
if m:
|
if m and m.group(1) == GL_USER:
|
||||||
return m.group(1)
|
return m.group(2)
|
||||||
finally:
|
finally:
|
||||||
f.close()
|
f.close()
|
||||||
return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change)
|
return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change)
|
||||||
|
@ -3228,7 +3325,7 @@ def __init__(self, user=None, repo=None, **kw):
|
||||||
self.__repo = repo
|
self.__repo = repo
|
||||||
|
|
||||||
def get_pusher(self):
|
def get_pusher(self):
|
||||||
return re.match('(.*?)\s*<', self.__user).group(1)
|
return re.match(r'(.*?)\s*<', self.__user).group(1)
|
||||||
|
|
||||||
def get_pusher_email(self):
|
def get_pusher_email(self):
|
||||||
return self.__user
|
return self.__user
|
||||||
|
@ -3262,7 +3359,7 @@ def get_pusher(self):
|
||||||
if self.__submitter.find('<') != -1:
|
if self.__submitter.find('<') != -1:
|
||||||
# Submitter has a configured email, we transformed
|
# Submitter has a configured email, we transformed
|
||||||
# __submitter into an RFC 2822 string already.
|
# __submitter into an RFC 2822 string already.
|
||||||
return re.match('(.*?)\s*<', self.__submitter).group(1)
|
return re.match(r'(.*?)\s*<', self.__submitter).group(1)
|
||||||
else:
|
else:
|
||||||
# Submitter has no configured email, it's just his name.
|
# Submitter has no configured email, it's just his name.
|
||||||
return self.__submitter
|
return self.__submitter
|
||||||
|
@ -3615,6 +3712,9 @@ def send_emails(self, mailer, body_filter=None):
|
||||||
|
|
||||||
for (num, sha1) in enumerate(sha1s):
|
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 len(rev.parents) > 1 and change.environment.excludemergerevisions:
|
||||||
|
# skipping a merge commit
|
||||||
|
continue
|
||||||
if not rev.recipients and rev.cc_recipients:
|
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.recipients = rev.cc_recipients
|
||||||
|
@ -3664,11 +3764,14 @@ def run_as_post_receive_hook(environment, mailer):
|
||||||
changes.append(
|
changes.append(
|
||||||
ReferenceChange.create(environment, oldrev, newrev, refname)
|
ReferenceChange.create(environment, oldrev, newrev, refname)
|
||||||
)
|
)
|
||||||
if changes:
|
if not changes:
|
||||||
push = Push(environment, changes)
|
mailer.close()
|
||||||
|
return
|
||||||
|
push = Push(environment, changes)
|
||||||
|
try:
|
||||||
push.send_emails(mailer, body_filter=environment.filter_body)
|
push.send_emails(mailer, body_filter=environment.filter_body)
|
||||||
if hasattr(mailer, '__del__'):
|
finally:
|
||||||
mailer.__del__()
|
mailer.close()
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
@ -3687,10 +3790,14 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=
|
||||||
refname,
|
refname,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
if not changes:
|
||||||
|
mailer.close()
|
||||||
|
return
|
||||||
push = Push(environment, changes, force_send)
|
push = Push(environment, changes, force_send)
|
||||||
push.send_emails(mailer, body_filter=environment.filter_body)
|
try:
|
||||||
if hasattr(mailer, '__del__'):
|
push.send_emails(mailer, body_filter=environment.filter_body)
|
||||||
mailer.__del__()
|
finally:
|
||||||
|
mailer.close()
|
||||||
|
|
||||||
|
|
||||||
def check_ref_filter(environment):
|
def check_ref_filter(environment):
|
||||||
|
@ -3860,7 +3967,7 @@ def build_environment_klass(env_name):
|
||||||
low_prec_mixin = known_env['lowprec']
|
low_prec_mixin = known_env['lowprec']
|
||||||
environment_mixins.append(low_prec_mixin)
|
environment_mixins.append(low_prec_mixin)
|
||||||
environment_mixins.append(Environment)
|
environment_mixins.append(Environment)
|
||||||
klass_name = env_name.capitalize() + 'Environement'
|
klass_name = env_name.capitalize() + 'Environment'
|
||||||
environment_klass = type(
|
environment_klass = type(
|
||||||
klass_name,
|
klass_name,
|
||||||
tuple(environment_mixins),
|
tuple(environment_mixins),
|
||||||
|
@ -4057,21 +4164,21 @@ def flush(self):
|
||||||
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)
|
self.loggers.append(error_log_file)
|
||||||
|
|
||||||
def info(self, msg):
|
def info(self, msg, *args, **kwargs):
|
||||||
for l in self.loggers:
|
for l in self.loggers:
|
||||||
l.info(msg)
|
l.info(msg, *args, **kwargs)
|
||||||
|
|
||||||
def debug(self, msg):
|
def debug(self, msg, *args, **kwargs):
|
||||||
for l in self.loggers:
|
for l in self.loggers:
|
||||||
l.debug(msg)
|
l.debug(msg, *args, **kwargs)
|
||||||
|
|
||||||
def warning(self, msg):
|
def warning(self, msg, *args, **kwargs):
|
||||||
for l in self.loggers:
|
for l in self.loggers:
|
||||||
l.warning(msg)
|
l.warning(msg, *args, **kwargs)
|
||||||
|
|
||||||
def error(self, msg):
|
def error(self, msg, *args, **kwargs):
|
||||||
for l in self.loggers:
|
for l in self.loggers:
|
||||||
l.error(msg)
|
l.error(msg, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
|
@ -4189,7 +4296,7 @@ def main(args):
|
||||||
show_env(environment, sys.stderr)
|
show_env(environment, sys.stderr)
|
||||||
|
|
||||||
if options.stdout or environment.stdout:
|
if options.stdout or environment.stdout:
|
||||||
mailer = OutputMailer(sys.stdout)
|
mailer = OutputMailer(sys.stdout, environment)
|
||||||
else:
|
else:
|
||||||
mailer = choose_mailer(config, environment)
|
mailer = choose_mailer(config, environment)
|
||||||
|
|
||||||
|
@ -4234,5 +4341,6 @@ def main(args):
|
||||||
sys.stderr.write(msg)
|
sys.stderr.write(msg)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main(sys.argv[1:])
|
main(sys.argv[1:])
|
||||||
|
|
|
@ -110,11 +110,12 @@ def is_section_empty(section, local):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
read_output(
|
read_output(
|
||||||
['git', 'config']
|
['git', 'config'] +
|
||||||
+ local_option
|
local_option +
|
||||||
+ ['--get-regexp', '^%s\.' % (section,)]
|
['--get-regexp', '^%s\.' % (section,)]
|
||||||
)
|
)
|
||||||
except CommandError, e:
|
except CommandError:
|
||||||
|
t, e, traceback = sys.exc_info()
|
||||||
if e.retcode == 1:
|
if e.retcode == 1:
|
||||||
# This means that no settings were found.
|
# This means that no settings were found.
|
||||||
return True
|
return True
|
||||||
|
@ -188,7 +189,9 @@ def migrate_config(strict=False, retain=False, overwrite=False):
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
|
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
|
||||||
)
|
)
|
||||||
new.set_recipients(name, old.get_recipients(name))
|
old_recipients = old.get_all(name, default=None)
|
||||||
|
old_recipients = ', '.join(o.strip() for o in old_recipients)
|
||||||
|
new.set_recipients(name, old_recipients)
|
||||||
|
|
||||||
if strict:
|
if strict:
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
|
|
|
@ -30,7 +30,6 @@ script's behavior could be changed or customized.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
|
||||||
|
|
||||||
# If necessary, add the path to the directory containing
|
# If necessary, add the path to the directory containing
|
||||||
# git_multimail.py to the Python path as follows. (This is not
|
# git_multimail.py to the Python path as follows. (This is not
|
||||||
|
@ -86,6 +85,7 @@ mailer = git_multimail.choose_mailer(config, environment)
|
||||||
|
|
||||||
# Use Python's smtplib to send emails. Both arguments are required.
|
# Use Python's smtplib to send emails. Both arguments are required.
|
||||||
#mailer = git_multimail.SMTPMailer(
|
#mailer = git_multimail.SMTPMailer(
|
||||||
|
# environment=environment,
|
||||||
# envelopesender='git-repo@example.com',
|
# envelopesender='git-repo@example.com',
|
||||||
# # The smtpserver argument can also include a port number; e.g.,
|
# # The smtpserver argument can also include a port number; e.g.,
|
||||||
# # smtpserver='mail.example.com:25'
|
# # smtpserver='mail.example.com:25'
|
||||||
|
|
Loading…
Reference in a new issue