1
1
#!/usr/bin/env python
2
2
# -*- coding: utf-8 -*-
5
# Licensed to the Apache Software Foundation (ASF) under one
6
# or more contributor license agreements. See the NOTICE file
7
# distributed with this work for additional information
8
# regarding copyright ownership. The ASF licenses this file
9
# to you under the Apache License, Version 2.0 (the
10
# "License"); you may not use this file except in compliance
11
# with the License. You may obtain a copy of the License at
13
# http://www.apache.org/licenses/LICENSE-2.0
15
# Unless required by applicable law or agreed to in writing,
16
# software distributed under the License is distributed on an
17
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
# KIND, either express or implied. See the License for the
19
# specific language governing permissions and limitations
4
23
# mailer.py: send email describing a commit
6
# $HeadURL: http://svn.apache.org/repos/asf/subversion/branches/1.6.x/tools/hook-scripts/mailer/mailer.py $
7
# $LastChangedDate: 2009-01-31 02:36:05 +0000 (Sat, 31 Jan 2009) $
8
# $LastChangedBy: arfrever $
9
# $LastChangedRevision: 875686 $
25
# $HeadURL: http://svn.apache.org/repos/asf/subversion/branches/1.7.x/tools/hook-scripts/mailer/mailer.py $
26
# $LastChangedDate: 2010-12-30 20:46:50 +0000 (Thu, 30 Dec 2010) $
27
# $LastChangedBy: hwright $
28
# $LastChangedRevision: 1053998 $
11
30
# USAGE: mailer.py commit REPOS REVISION [CONFIG-FILE]
12
31
# mailer.py propchange REPOS REVISION AUTHOR REVPROPNAME [CONFIG-FILE]
222
240
'MIME-Version: 1.0\n' \
223
241
'Content-Type: text/plain; charset=UTF-8\n' \
224
242
'Content-Transfer-Encoding: 8bit\n' \
225
% (self.from_addr, ', '.join(self.to_addrs), subject)
243
'X-Svn-Commit-Project: %s\n' \
244
'X-Svn-Commit-Author: %s\n' \
245
'X-Svn-Commit-Revision: %d\n' \
246
'X-Svn-Commit-Repository: %s\n' \
247
% (self.from_addr, ', '.join(self.to_addrs), subject,
248
group, self.repos.author or 'no_author', self.repos.rev,
249
os.path.basename(self.repos.repos_dir))
226
250
if self.reply_to:
227
251
hdrs = '%sReply-To: %s\n' % (hdrs, self.reply_to)
228
252
return hdrs + '\n'
324
348
self.changelist = sorted(editor.get_changes().items())
350
log = repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or ''
326
352
# collect the set of groups and the unique sets of params for the options
327
353
self.groups = { }
328
354
for path, change in self.changelist:
329
for (group, params) in self.cfg.which_groups(path):
355
for (group, params) in self.cfg.which_groups(path, log):
330
356
# turn the params into a hashable object and stash it away
331
357
param_list = sorted(params.items())
332
358
# collect the set of paths belonging to this group
398
424
# collect the set of groups and the unique sets of params for the options
399
425
self.groups = { }
400
for (group, params) in self.cfg.which_groups(''):
426
for (group, params) in self.cfg.which_groups('', None):
401
427
# turn the params into a hashable object and stash it away
402
428
param_list = sorted(params.items())
403
429
self.groups[group, tuple(param_list)] = params
487
513
# collect the set of groups and the unique sets of params for the options
488
514
self.groups = { }
489
515
for path in self.dirlist:
490
for (group, params) in self.cfg.which_groups(path):
516
for (group, params) in self.cfg.which_groups(path, None):
491
517
# turn the params into a hashable object and stash it away
492
518
param_list = sorted(params.items())
493
519
# collect the set of paths belonging to this group
840
866
content = src_fname = dst_fname = None
842
868
src_fname, dst_fname = diff.get_files()
843
content = DiffContent(self.cfg.get_diff_cmd(self.group, {
844
'label_from' : label1,
870
content = DiffContent(self.cfg.get_diff_cmd(self.group, {
871
'label_from' : label1,
877
# diff command does not exist, try difflib.unified_diff()
878
content = DifflibDiffContent(label1, label2, src_fname, dst_fname)
850
880
# return a data item for this diff
897
def _classify_diff_line(line, seen_change):
898
# classify the type of line.
920
line=line[0:-2] + '\n' # remove carriage return
922
return line, ltype, seen_change
868
925
class DiffContent:
869
926
"This is a generator-like object returning annotated lines of a diff."
894
# classify the type of line.
897
self.seen_change = True
915
line=line[0:-2] + '\n' # remove carriage return
919
text=line[1:-1], # remove indicator and newline
951
line, ltype, self.seen_change = _classify_diff_line(line, self.seen_change)
954
text=line[1:-1], # remove indicator and newline
958
class DifflibDiffContent():
959
"This is a generator-like object returning annotated lines of a diff."
961
def __init__(self, label_from, label_to, from_file, to_file):
963
self.seen_change = False
964
fromlines = open(from_file, 'U').readlines()
965
tolines = open(to_file, 'U').readlines()
966
self.diff = difflib.unified_diff(fromlines, tolines,
967
label_from, label_to)
969
def __nonzero__(self):
970
# we always have some items
973
def __getitem__(self, idx):
976
line = self.diff.next()
977
except StopIteration:
980
line, ltype, self.seen_change = _classify_diff_line(line, self.seen_change)
983
text=line[1:-1], # remove indicator and newline
924
987
class TextCommitRenderer:
925
988
"This class will render the commit mail in plain text."
1239
1302
exclude_paths_re = None
1241
self._group_re.append((group, re.compile(for_paths),
1242
exclude_paths_re, params))
1304
# check search_logmsg re
1305
search_logmsg = getattr(sub, 'search_logmsg', None)
1306
if search_logmsg is not None:
1307
search_logmsg_re = re.compile(search_logmsg)
1309
search_logmsg_re = None
1311
self._group_re.append((group,
1312
re.compile(for_paths),
1244
1317
# after all the groups are done, add in the default group
1246
1319
self._group_re.append((None,
1247
1320
re.compile(self.defaults.for_paths),
1249
self._default_params))
1322
self._default_params,
1250
1324
except AttributeError:
1251
1325
# there is no self.defaults.for_paths
1254
def which_groups(self, path):
1328
def which_groups(self, path, logmsg):
1255
1329
"Return the path's associated groups."
1257
for group, pattern, exclude_pattern, repos_params in self._group_re:
1331
for group, pattern, exclude_pattern, repos_params, search_logmsg_re in self._group_re:
1258
1332
match = pattern.match(path)
1260
1334
if exclude_pattern and exclude_pattern.match(path):
1262
1336
params = repos_params.copy()
1263
1337
params.update(match.groupdict())
1264
groups.append((group, params))
1339
if search_logmsg_re is None:
1340
groups.append((group, params))
1345
for match in search_logmsg_re.finditer(logmsg):
1346
# Add captured variables to (a copy of) params
1347
msg_params = params.copy()
1348
msg_params.update(match.groupdict())
1349
groups.append((group, msg_params))
1266
1352
groups.append((None, self._default_params))