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>
Matthieu Moy committed
Jan 7, 2019 at 18:48 UTC
99177b34db1d473e8f90544cf0bf83f47308e9ad
8 files changed
+281
-57
contrib/hooks/multimail/CHANGES
+56
@@ -1,3 +1,59 @@
1
+Release 1.5.0
2
+=============
3
+
4
+Backward-incompatible change
5
+----------------------------
6
+
7
+The name of classes for environment was misnamed as `*Environement`.
8
+It is now `*Environment`.
9
+
10
+New features
11
+------------
12
+
13
+* A Thread-Index header is now added to each email sent (except for
14
+ combined emails where it would not make sense), so that MS Outlook
15
+ properly groups messages by threads even though they have a
16
+ different subject line. Unfortunately, even adding this header the
17
+ threading still seems to be unreliable, but it is unclear whether
18
+ this is an issue on our side or on MS Outlook's side (see discussion
19
+ here: https://github.com/git-multimail/git-multimail/pull/194).
20
+
21
+* A new variable multimailhook.ExcludeMergeRevisions was added to send
22
+ notification emails only for non-merge commits.
23
+
24
+* For gitolite environment, it is now possible to specify the mail map
25
+ in a separate file in addition to gitolite.conf, using the variable
26
+ multimailhook.MailaddressMap.
27
+
28
+Internal changes
29
+----------------
30
+
31
+* The testsuite now uses GIT_PRINT_SHA1_ELLIPSIS where needed for
32
+ compatibility with recent Git versions. Only tests are affected.
33
+
34
+* We don't try to install pyflakes in the continuous integration job
35
+ for old Python versions where it's no longer available.
36
+
37
+* Stop using the deprecated cgi.escape in Python 3.
38
+
39
+* New flake8 warnings have been fixed.
40
+
41
+* Python 3.6 is now tested against on Travis-CI.
42
+
43
+* A bunch of lgtm.com warnings have been fixed.
44
+
45
+Bug fixes
46
+---------
47
+
48
+* SMTPMailer logs in only once now. It used to re-login for each email
49
+ sent which triggered errors for some SMTP servers.
50
+
51
+* migrate-mailhook-config was broken by internal refactoring, it
52
+ should now work again.
53
+
54
+This version was tested with Python 2.6 to 3.7. It was tested with Git
55
+1.7.10.406.gdc801, 2.15.1 and 2.20.1.98.gecbdaf0.
56
+
57
Release 1.4.0
58
=============
59
contrib/hooks/multimail/CONTRIBUTING.rst
+25
-3
@@ -4,9 +4,8 @@ Contributing
4
git-multimail is an open-source project, built by volunteers. We would
5
welcome your help!
6
7
-The current maintainers are Matthieu Moy
8
-<matthieu.moy@grenoble-inp.fr> and Michael Haggerty
9
-<mhagger@alum.mit.edu>.
7
+The current maintainers are `Matthieu Moy <http://matthieu-moy.fr>`__ and
8
+`Michael Haggerty <https://github.com/mhagger>`__.
9
10
Please note that although a copy of git-multimail is distributed in
11
the "contrib" section of the main Git project, development takes place
@@ -33,6 +32,29 @@ mailing list`_.
32
Please CC emails regarding git-multimail to the maintainers so that we
33
don't overlook them.
34
35
+Help needed: testers/maintainer for specific environments/OS
36
+------------------------------------------------------------
37
+
38
+The current maintainer uses and tests git-multimail on Linux with the
39
+Generic environment. More testers, or better contributors are needed
40
+to test git-multimail on other real-life setups:
41
+
42
+* Mac OS X, Windows: git-multimail is currently not supported on these
43
+ platforms. But since we have no external dependencies and try to
44
+ write code as portable as possible, it is possible that
45
+ git-multimail already runs there and if not, it is likely that it
46
+ could be ported easily.
47
+
48
+ Patches to improve support for Windows and OS X are welcome.
49
+ Ideally, there would be a sub-maintainer for each OS who would test
50
+ at least once before each release (around twice a year).
51
+
52
+* Gerrit, Stash, Gitolite environments: although the testsuite
53
+ contains tests for these environments, a tester/maintainer for each
54
+ environment would be welcome to test and report failure (or success)
55
+ on real-life environments periodically (here also, feedback before
56
+ each release would be highly appreciated).
57
+
58
59
.. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail
60
.. _`Git mailing list`: git@vger.kernel.org
contrib/hooks/multimail/README.Git
+2
-2
@@ -6,10 +6,10 @@ website:
6
https://github.com/git-multimail/git-multimail
7
8
The version in this directory was obtained from the upstream project
9
-on August 17 2016 and consists of the "git-multimail" subdirectory from
9
+on January 07 2019 and consists of the "git-multimail" subdirectory from
10
revision
11
12
- 07b1cb6bfd7be156c62e1afa17cae13b850a869f refs/tags/1.4.0
12
+ 04e80e6c40be465cc62b6c246f0fcb8fd2cfd454 refs/tags/1.5.0
13
14
Please see the README file in this directory for information about how
15
to report bugs or contribute to git-multimail.
contrib/hooks/multimail/README.rst
renamed
+32
-6
@@ -1,4 +1,4 @@
1
-git-multimail version 1.4.0
1
+git-multimail version 1.5.0
2
===========================
3
4
.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
@@ -20,8 +20,8 @@ GPLv2 (see the COPYING file for details).
20
21
Please note: although, as a convenience, git-multimail may be
22
distributed along with the main Git project, development of
23
-git-multimail takes place in its own, separate project. See section
24
-"Getting involved" below for more information.
23
+git-multimail takes place in its own, separate project. Please, read
24
+`<CONTRIBUTING.rst>`__ for more information.
25
26
27
By default, for each push received by the repository, git-multimail:
@@ -89,6 +89,10 @@ Requirements
89
the multimailhook.mailer configuration variable below for how to
90
configure git-multimail to send emails via an SMTP server.
91
92
+* git-multimail is currently tested only on Linux. It may or may not
93
+ work on other platforms such as Windows and Mac OS. See
94
+ `<CONTRIBUTING.rst>`__ to improve the situation.
95
+
96
97
Invocation
98
----------
@@ -369,7 +373,7 @@ multimailhook.mailer
373
unset, then the value of multimailhook.from is used.
374
375
multimailhook.smtpServerTimeout
372
- Timeout in seconds.
376
+ Timeout in seconds. Default is 10.
377
378
multimailhook.smtpEncryption
379
Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls).
@@ -419,8 +423,20 @@ multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
423
If config values are unset, the value of the From: header is
424
determined as follows:
425
422
- 1. (gitolite environment only) Parse gitolite.conf, looking for a
423
- block of comments that looks like this::
426
+ 1. (gitolite environment only)
427
+ 1.a) If ``multimailhook.MailaddressMap`` is set, and is a path
428
+ to an existing file (if relative, it is considered relative to
429
+ the place where ``gitolite.conf`` is located), then this file
430
+ should contain lines like::
431
+
432
+ username Firstname Lastname <email@example.com>
433
+
434
+ git-multimail will then look for a line where ``$GL_USER``
435
+ matches the ``username`` part, and use the rest of the line for
436
+ the ``From:`` header.
437
+
438
+ 1.b) Parse gitolite.conf, looking for a block of comments that
439
+ looks like this::
440
441
# BEGIN USER EMAILS
442
# username Firstname Lastname <email@example.com>
@@ -436,6 +452,11 @@ multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
452
453
3. Use the value of multimailhook.envelopeSender.
454
455
+multimailhook.MailaddressMap
456
+ (gitolite environment only)
457
+ File to look for a ``From:`` address based on the user doing the
458
+ push. Defaults to unset. See ``multimailhook.from`` for details.
459
+
460
multimailhook.administrator
461
The name and/or email address of the administrator of the Git
462
repository; used in FOOTER_TEMPLATE. Default is
@@ -484,6 +505,11 @@ multimailhook.maxCommitEmails
505
mailbombing, for example on an initial push. To disable commit
506
emails limit, set this option to 0. The default is 500.
507
508
+multimailhook.excludeMergeRevisions
509
+ When sending out revision emails, do not consider merge commits (the
510
+ functional equivalent of `rev-list --no-merges`).
511
+ The default is `false` (send merge commit emails).
512
+
513
multimailhook.emailStrictUTF8
514
If this boolean option is set to `true`, then the main part of the
515
email body is forced to be valid UTF-8. Any characters that are
contrib/hooks/multimail/doc/gitolite.rst
+9
@@ -46,6 +46,15 @@ and add::
46
config multimailhook.mailingList = # Where emails should be sent
47
config multimailhook.from = # From address to use
48
49
+Note that by default, gitolite forbids ``<`` and ``>`` in variable
50
+values (for security/paranoia reasons, see
51
+`compensating for UNSAFE_PATT
52
+<http://gitolite.com/gitolite/git-config/index.html#compensating-for-unsafe95patt>`__
53
+in gitolite's documentation for explanations and a way to disable
54
+this). As a consequence, you will not be able to use ``First Last
55
+<First.Last@example.com>`` as recipient email, but specifying
56
+``First.Last@example.com`` alone works.
57
+
58
Obviously, you can customize all parameters on a per-repository basis by
59
adding these ``config multimailhook.*`` lines in the section
60
corresponding to a repository or set of repositories.
contrib/hooks/multimail/git_multimail.py
+148
-40
@@ -1,6 +1,6 @@
1
#! /usr/bin/env python
2
3
-__version__ = '1.4.0'
3
+__version__ = '1.5.0'
4
5
# Copyright (c) 2015-2016 Matthieu Moy and others
6
# Copyright (c) 2012-2014 Michael Haggerty and others
@@ -64,7 +64,9 @@ except ImportError:
64
# Python < 2.6 do not have ssl, but that's OK if we don't use it.
65
pass
66
import time
67
-import cgi
67
+
68
+import uuid
69
+import base64
70
71
PYTHON3 = sys.version_info >= (3, 0)
72
@@ -73,7 +75,7 @@ if sys.version_info <= (2, 5):
75
for element in iterable:
76
if not element:
77
return False
76
- return True
78
+ return True
79
80
81
def is_ascii(s):
@@ -108,6 +110,12 @@ if PYTHON3:
110
return out.decode(sys.getdefaultencoding())
111
except UnicodeEncodeError:
112
return out.decode(ENCODING)
113
+
114
+ import html
115
+
116
+ def html_escape(s):
117
+ return html.escape(s)
118
+
119
else:
120
def is_string(s):
121
try:
@@ -130,6 +138,10 @@ else:
138
def next(it):
139
return it.next()
140
141
+ import cgi
142
+
143
+ def html_escape(s):
144
+ return cgi.escape(s, True)
145
146
try:
147
from email.charset import Charset
@@ -190,6 +202,7 @@ Content-Transfer-Encoding: 8bit
202
Message-ID: %(msgid)s
203
From: %(fromaddr)s
204
Reply-To: %(reply_to)s
205
+Thread-Index: %(thread_index)s
206
X-Git-Host: %(fqdn)s
207
X-Git-Repo: %(repo_shortname)s
208
X-Git-Refname: %(refname)s
@@ -322,6 +335,7 @@ From: %(fromaddr)s
335
Reply-To: %(reply_to)s
336
In-Reply-To: %(reply_to_msgid)s
337
References: %(reply_to_msgid)s
338
+Thread-Index: %(thread_index)s
339
X-Git-Host: %(fqdn)s
340
X-Git-Repo: %(repo_shortname)s
341
X-Git-Refname: %(refname)s
@@ -763,6 +777,9 @@ class GitObject(object):
777
def __eq__(self, other):
778
return isinstance(other, GitObject) and self.sha1 == other.sha1
779
780
+ def __ne__(self, other):
781
+ return not self == other
782
+
783
def __hash__(self):
784
return hash(self.sha1)
785
@@ -852,7 +869,7 @@ class Change(object):
869
if html_escape_val:
870
for k in values:
871
if is_string(values[k]):
855
- values[k] = cgi.escape(values[k], True)
872
+ values[k] = html_escape(values[k])
873
for line in template.splitlines(True):
874
yield line % values
875
@@ -909,7 +926,7 @@ class Change(object):
926
927
raise NotImplementedError()
928
912
- def generate_email_body(self):
929
+ def generate_email_body(self, push):
930
"""Generate the main part of the email body, a line at a time.
931
932
The text in the body might be truncated after a specified
@@ -936,7 +953,7 @@ class Change(object):
953
yield "<pre style='margin:0'>\n"
954
955
for line in lines:
939
- yield cgi.escape(line)
956
+ yield html_escape(line)
957
958
yield '</pre>\n'
959
else:
@@ -1011,7 +1028,7 @@ class Change(object):
1028
fgcolor = '404040'
1029
1030
# Chop the trailing LF, we don't want it inside <pre>.
1014
- line = cgi.escape(line[:-1])
1031
+ line = html_escape(line[:-1])
1032
1033
if bgcolor or fgcolor:
1034
style = 'display:block; white-space:pre;'
@@ -1060,6 +1077,10 @@ class Revision(Change):
1077
self.author = read_git_output(['log', '--no-walk', '--format=%aN <%aE>', self.rev.sha1])
1078
self.recipients = self.environment.get_revision_recipients(self)
1079
1080
+ # -s is short for --no-patch, but -s works on older git's (e.g. 1.7)
1081
+ self.parents = read_git_lines(['show', '-s', '--format=%P',
1082
+ self.rev.sha1])[0].split()
1083
+
1084
self.cc_recipients = ''
1085
if self.environment.get_scancommitforcc():
1086
self.cc_recipients = ', '.join(to.strip() for to in self._cc_recipients())
@@ -1090,6 +1111,7 @@ class Revision(Change):
1111
oneline = oneline[:max_subject_length - 6] + ' [...]'
1112
1113
values['rev'] = self.rev.sha1
1114
+ values['parents'] = ' '.join(self.parents)
1115
values['rev_short'] = self.rev.short
1116
values['change_type'] = self.change_type
1117
values['refname'] = self.refname
@@ -1097,6 +1119,7 @@ class Revision(Change):
1119
values['short_refname'] = self.reference_change.short_refname
1120
values['refname_type'] = self.reference_change.refname_type
1121
values['reply_to_msgid'] = self.reference_change.msgid
1122
+ values['thread_index'] = self.reference_change.thread_index
1123
values['num'] = self.num
1124
values['tot'] = self.tot
1125
values['recipients'] = self.recipients
@@ -1244,6 +1267,23 @@ class ReferenceChange(Change):
1267
old=old, new=new, rev=rev,
1268
)
1269
1270
+ @staticmethod
1271
+ def make_thread_index():
1272
+ """Return a string appropriate for the Thread-Index header,
1273
+ needed by MS Outlook to get threading right.
1274
+
1275
+ The format is (base64-encoded):
1276
+ - 1 byte must be 1
1277
+ - 5 bytes encode a date (hardcoded here)
1278
+ - 16 bytes for a globally unique identifier
1279
+
1280
+ FIXME: Unfortunately, even with the Thread-Index field, MS
1281
+ Outlook doesn't seem to do the threading reliably (see
1282
+ https://github.com/git-multimail/git-multimail/pull/194).
1283
+ """
1284
+ thread_index = b'\x01\x00\x00\x12\x34\x56' + uuid.uuid4().bytes
1285
+ return base64.standard_b64encode(thread_index).decode('ascii')
1286
+
1287
def __init__(self, environment, refname, short_refname, old, new, rev):
1288
Change.__init__(self, environment)
1289
self.change_type = {
@@ -1257,6 +1297,7 @@ class ReferenceChange(Change):
1297
self.new = new
1298
self.rev = rev
1299
self.msgid = make_msgid()
1300
+ self.thread_index = self.make_thread_index()
1301
self.diffopts = environment.diffopts
1302
self.graphopts = environment.graphopts
1303
self.logopts = environment.logopts
@@ -1276,6 +1317,7 @@ class ReferenceChange(Change):
1317
values['refname'] = self.refname
1318
values['short_refname'] = self.short_refname
1319
values['msgid'] = self.msgid
1320
+ values['thread_index'] = self.thread_index
1321
values['recipients'] = self.recipients
1322
values['oldrev'] = str(self.old)
1323
values['oldrev_short'] = self.old.short
@@ -1941,6 +1983,9 @@ class Mailer(object):
1983
def __init__(self, environment):
1984
self.environment = environment
1985
1986
+ def close(self):
1987
+ pass
1988
+
1989
def send(self, lines, to_addrs):
1990
"""Send an email consisting of lines.
1991
@@ -2054,6 +2099,7 @@ class SMTPMailer(Mailer):
2099
self.username = smtpuser
2100
self.password = smtppass
2101
self.smtpcacerts = smtpcacerts
2102
+ self.loggedin = False
2103
try:
2104
def call(klass, server, timeout):
2105
try:
@@ -2130,20 +2176,30 @@ class SMTPMailer(Mailer):
2176
% (self.smtpserver, sys.exc_info()[1]))
2177
sys.exit(1)
2178
2133
- def __del__(self):
2179
+ def close(self):
2180
if hasattr(self, 'smtp'):
2181
self.smtp.quit()
2182
del self.smtp
2183
2184
+ def __del__(self):
2185
+ self.close()
2186
+
2187
def send(self, lines, to_addrs):
2188
try:
2189
if self.username or self.password:
2141
- self.smtp.login(self.username, self.password)
2190
+ if not self.loggedin:
2191
+ self.smtp.login(self.username, self.password)
2192
+ self.loggedin = True
2193
msg = ''.join(lines)
2194
# turn comma-separated list into Python list if needed.
2195
if is_string(to_addrs):
2196
to_addrs = [email for (name, email) in getaddresses([to_addrs])]
2197
self.smtp.sendmail(self.envelopesender, to_addrs, msg)
2198
+ except socket.timeout:
2199
+ self.environment.get_logger().error(
2200
+ '*** Error sending email ***\n'
2201
+ '*** SMTP server timed out (timeout is %s)\n'
2202
+ % self.smtpservertimeout)
2203
except smtplib.SMTPResponseException:
2204
err = sys.exc_info()[1]
2205
self.environment.get_logger().error(
@@ -2171,7 +2227,8 @@ class OutputMailer(Mailer):
2227
2228
SEPARATOR = '=' * 75 + '\n'
2229
2174
- def __init__(self, f):
2230
+ def __init__(self, f, environment=None):
2231
+ super(OutputMailer, self).__init__(environment=environment)
2232
self.f = f
2233
2234
def send(self, lines, to_addrs):
@@ -2382,6 +2439,7 @@ class Environment(object):
2439
self.html_in_footer = False
2440
self.commitBrowseURL = None
2441
self.maxcommitemails = 500
2442
+ self.excludemergerevisions = False
2443
self.diffopts = ['--stat', '--summary', '--find-copies-harder']
2444
self.graphopts = ['--oneline', '--decorate']
2445
self.logopts = []
@@ -2621,6 +2679,8 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
2679
2680
self.commitBrowseURL = config.get('commitBrowseURL')
2681
2682
+ self.excludemergerevisions = config.get('excludeMergeRevisions')
2683
+
2684
maxcommitemails = config.get('maxcommitemails')
2685
if maxcommitemails is not None:
2686
try:
@@ -3152,7 +3212,10 @@ class GitoliteEnvironmentHighPrecMixin(Environment):
3212
return self.osenv.get('GL_USER', 'unknown user')
3213
3214
3155
-class GitoliteEnvironmentLowPrecMixin(Environment):
3215
+class GitoliteEnvironmentLowPrecMixin(
3216
+ ConfigEnvironmentMixin,
3217
+ Environment):
3218
+
3219
def get_repo_shortname(self):
3220
# The gitolite environment variable $GL_REPO is a pretty good
3221
# repo_shortname (though it's probably not as good as a value
@@ -3162,6 +3225,16 @@ class GitoliteEnvironmentLowPrecMixin(Environment):
3225
super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname()
3226
)
3227
3228
+ @staticmethod
3229
+ def _compile_regex(re_template):
3230
+ return (
3231
+ re.compile(re_template % x)
3232
+ for x in (
3233
+ r'BEGIN\s+USER\s+EMAILS',
3234
+ r'([^\s]+)\s+(.*)',
3235
+ r'END\s+USER\s+EMAILS',
3236
+ ))
3237
+
3238
def get_fromaddr(self, change=None):
3239
GL_USER = self.osenv.get('GL_USER')
3240
if GL_USER is not None:
@@ -3174,18 +3247,42 @@ class GitoliteEnvironmentLowPrecMixin(Environment):
3247
GL_CONF = self.osenv.get(
3248
'GL_CONF',
3249
os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf'))
3250
+
3251
+ mailaddress_map = self.config.get('MailaddressMap')
3252
+ # If relative, consider relative to GL_CONF:
3253
+ if mailaddress_map:
3254
+ mailaddress_map = os.path.join(os.path.dirname(GL_CONF),
3255
+ mailaddress_map)
3256
+ if os.path.isfile(mailaddress_map):
3257
+ f = open(mailaddress_map, 'rU')
3258
+ try:
3259
+ # Leading '#' is optional
3260
+ re_begin, re_user, re_end = self._compile_regex(
3261
+ r'^(?:\s*#)?\s*%s\s*$')
3262
+ for l in f:
3263
+ l = l.rstrip('\n')
3264
+ if re_begin.match(l) or re_end.match(l):
3265
+ continue # Ignore these lines
3266
+ m = re_user.match(l)
3267
+ if m:
3268
+ if m.group(1) == GL_USER:
3269
+ return m.group(2)
3270
+ else:
3271
+ continue # Not this user, but not an error
3272
+ raise ConfigurationException(
3273
+ "Syntax error in mail address map.\n"
3274
+ "Check file {}.\n"
3275
+ "Line: {}".format(mailaddress_map, l))
3276
+
3277
+ finally:
3278
+ f.close()
3279
+
3280
if os.path.isfile(GL_CONF):
3281
f = open(GL_CONF, 'rU')
3282
try:
3283
in_user_emails_section = False
3181
- re_template = r'^\s*#\s*%s\s*$'
3182
- re_begin, re_user, re_end = (
3183
- re.compile(re_template % x)
3184
- for x in (
3185
- r'BEGIN\s+USER\s+EMAILS',
3186
- re.escape(GL_USER) + r'\s+(.*)',
3187
- r'END\s+USER\s+EMAILS',
3188
- ))
3284
+ re_begin, re_user, re_end = self._compile_regex(
3285
+ r'^\s*#\s*%s\s*$')
3286
for l in f:
3287
l = l.rstrip('\n')
3288
if not in_user_emails_section:
@@ -3195,8 +3292,8 @@ class GitoliteEnvironmentLowPrecMixin(Environment):
3292
if re_end.match(l):
3293
break
3294
m = re_user.match(l)
3198
- if m:
3199
- return m.group(1)
3295
+ if m and m.group(1) == GL_USER:
3296
+ return m.group(2)
3297
finally:
3298
f.close()
3299
return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change)
@@ -3228,7 +3325,7 @@ class StashEnvironmentHighPrecMixin(Environment):
3325
self.__repo = repo
3326
3327
def get_pusher(self):
3231
- return re.match('(.*?)\s*<', self.__user).group(1)
3328
+ return re.match(r'(.*?)\s*<', self.__user).group(1)
3329
3330
def get_pusher_email(self):
3331
return self.__user
@@ -3262,7 +3359,7 @@ class GerritEnvironmentHighPrecMixin(Environment):
3359
if self.__submitter.find('<') != -1:
3360
# Submitter has a configured email, we transformed
3361
# __submitter into an RFC 2822 string already.
3265
- return re.match('(.*?)\s*<', self.__submitter).group(1)
3362
+ return re.match(r'(.*?)\s*<', self.__submitter).group(1)
3363
else:
3364
# Submitter has no configured email, it's just his name.
3365
return self.__submitter
@@ -3615,6 +3712,9 @@ class Push(object):
3712
3713
for (num, sha1) in enumerate(sha1s):
3714
rev = Revision(change, GitObject(sha1), num=num + 1, tot=len(sha1s))
3715
+ if len(rev.parents) > 1 and change.environment.excludemergerevisions:
3716
+ # skipping a merge commit
3717
+ continue
3718
if not rev.recipients and rev.cc_recipients:
3719
change.environment.log_msg('*** Replacing Cc: with To:')
3720
rev.recipients = rev.cc_recipients
@@ -3664,11 +3764,14 @@ def run_as_post_receive_hook(environment, mailer):
3764
changes.append(
3765
ReferenceChange.create(environment, oldrev, newrev, refname)
3766
)
3667
- if changes:
3668
- push = Push(environment, changes)
3767
+ if not changes:
3768
+ mailer.close()
3769
+ return
3770
+ push = Push(environment, changes)
3771
+ try:
3772
push.send_emails(mailer, body_filter=environment.filter_body)
3670
- if hasattr(mailer, '__del__'):
3671
- mailer.__del__()
3773
+ finally:
3774
+ mailer.close()
3775
3776
3777
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=
3790
refname,
3791
),
3792
]
3793
+ if not changes:
3794
+ mailer.close()
3795
+ return
3796
push = Push(environment, changes, force_send)
3691
- push.send_emails(mailer, body_filter=environment.filter_body)
3692
- if hasattr(mailer, '__del__'):
3693
- mailer.__del__()
3797
+ try:
3798
+ push.send_emails(mailer, body_filter=environment.filter_body)
3799
+ finally:
3800
+ mailer.close()
3801
3802
3803
def check_ref_filter(environment):
@@ -3860,7 +3967,7 @@ def build_environment_klass(env_name):
3967
low_prec_mixin = known_env['lowprec']
3968
environment_mixins.append(low_prec_mixin)
3969
environment_mixins.append(Environment)
3863
- klass_name = env_name.capitalize() + 'Environement'
3970
+ klass_name = env_name.capitalize() + 'Environment'
3971
environment_klass = type(
3972
klass_name,
3973
tuple(environment_mixins),
@@ -4057,21 +4164,21 @@ class Logger(object):
4164
environment, 'git_multimail.error', environment.error_log_file, logging.ERROR)
4165
self.loggers.append(error_log_file)
4166
4060
- def info(self, msg):
4167
+ def info(self, msg, *args, **kwargs):
4168
for l in self.loggers:
4062
- l.info(msg)
4169
+ l.info(msg, *args, **kwargs)
4170
4064
- def debug(self, msg):
4171
+ def debug(self, msg, *args, **kwargs):
4172
for l in self.loggers:
4066
- l.debug(msg)
4173
+ l.debug(msg, *args, **kwargs)
4174
4068
- def warning(self, msg):
4175
+ def warning(self, msg, *args, **kwargs):
4176
for l in self.loggers:
4070
- l.warning(msg)
4177
+ l.warning(msg, *args, **kwargs)
4178
4072
- def error(self, msg):
4179
+ def error(self, msg, *args, **kwargs):
4180
for l in self.loggers:
4074
- l.error(msg)
4181
+ l.error(msg, *args, **kwargs)
4182
4183
4184
def main(args):
@@ -4189,7 +4296,7 @@ def main(args):
4296
show_env(environment, sys.stderr)
4297
4298
if options.stdout or environment.stdout:
4192
- mailer = OutputMailer(sys.stdout)
4299
+ mailer = OutputMailer(sys.stdout, environment)
4300
else:
4301
mailer = choose_mailer(config, environment)
4302
@@ -4234,5 +4341,6 @@ def main(args):
4341
sys.stderr.write(msg)
4342
sys.exit(1)
4343
4344
+
4345
if __name__ == '__main__':
4346
main(sys.argv[1:])
contrib/hooks/multimail/migrate-mailhook-config
+8
-5
@@ -110,11 +110,12 @@ def is_section_empty(section, local):
110
111
try:
112
read_output(
113
- ['git', 'config']
114
- + local_option
115
- + ['--get-regexp', '^%s\.' % (section,)]
113
+ ['git', 'config'] +
114
+ local_option +
115
+ ['--get-regexp', '^%s\.' % (section,)]
116
)
117
- except CommandError, e:
117
+ except CommandError:
118
+ t, e, traceback = sys.exc_info()
119
if e.retcode == 1:
120
# This means that no settings were found.
121
return True
@@ -188,7 +189,9 @@ def migrate_config(strict=False, retain=False, overwrite=False):
189
sys.stderr.write(
190
'...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
191
)
191
- new.set_recipients(name, old.get_recipients(name))
192
+ old_recipients = old.get_all(name, default=None)
193
+ old_recipients = ', '.join(o.strip() for o in old_recipients)
194
+ new.set_recipients(name, old_recipients)
195
196
if strict:
197
sys.stderr.write(
contrib/hooks/multimail/post-receive.example
+1
-1
@@ -30,7 +30,6 @@ script's behavior could be changed or customized.
30
"""
31
32
import sys
33
-import os
33
34
# If necessary, add the path to the directory containing
35
# git_multimail.py to the Python path as follows. (This is not
@@ -86,6 +85,7 @@ mailer = git_multimail.choose_mailer(config, environment)
85
86
# Use Python's smtplib to send emails. Both arguments are required.
87
#mailer = git_multimail.SMTPMailer(
88
+# environment=environment,
89
# envelopesender='git-repo@example.com',
90
# # The smtpserver argument can also include a port number; e.g.,
91
# # smtpserver='mail.example.com:25'