~ubuntu-branches/ubuntu/trusty/subversion/trusty-proposed

« back to all changes in this revision

Viewing changes to tools/hook-scripts/mailer/mailer.py

  • Committer: Package Import Robot
  • Author(s): Andy Whitcroft
  • Date: 2012-06-21 15:36:36 UTC
  • mfrom: (0.4.13 sid)
  • Revision ID: package-import@ubuntu.com-20120621153636-amqqmuidgwgxz1ly
Tags: 1.7.5-1ubuntu1
* Merge from Debian unstable.  Remaining changes:
  - Create pot file on build.
  - Build a python-subversion-dbg package.
  - Build-depend on python-dbg.
  - Build-depend on default-jre-headless/-jdk.
  - Do not apply java-build patch.
  - debian/rules: Manually create the doxygen output directory, otherwise
    we get weird build failures when running parallel builds.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/env python
2
2
# -*- coding: utf-8 -*-
3
3
#
 
4
#
 
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
 
12
#
 
13
#   http://www.apache.org/licenses/LICENSE-2.0
 
14
#
 
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
 
20
# under the License.
 
21
#
 
22
#
4
23
# mailer.py: send email describing a commit
5
24
#
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 $
10
29
#
11
30
# USAGE: mailer.py commit      REPOS REVISION [CONFIG-FILE]
12
31
#        mailer.py propchange  REPOS REVISION AUTHOR REVPROPNAME [CONFIG-FILE]
46
65
import smtplib
47
66
import re
48
67
import tempfile
49
 
import types
50
68
 
51
69
# Minimal version of Subversion's bindings required
52
70
_MIN_SVN_VERSION = [1, 5, 0]
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'
323
347
 
324
348
    self.changelist = sorted(editor.get_changes().items())
325
349
 
 
350
    log = repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or ''
 
351
 
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
397
423
 
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
708
734
    return True
709
735
 
710
736
  def __getitem__(self, idx):
711
 
    while 1:
 
737
    while True:
712
738
      if self.idx == len(self.changelist):
713
739
        raise IndexError
714
740
 
840
866
          content = src_fname = dst_fname = None
841
867
        else:
842
868
          src_fname, dst_fname = diff.get_files()
843
 
          content = DiffContent(self.cfg.get_diff_cmd(self.group, {
844
 
            'label_from' : label1,
845
 
            'label_to' : label2,
846
 
            'from' : src_fname,
847
 
            'to' : dst_fname,
848
 
            }))
 
869
          try:
 
870
            content = DiffContent(self.cfg.get_diff_cmd(self.group, {
 
871
              'label_from' : label1,
 
872
              'label_to' : label2,
 
873
              'from' : src_fname,
 
874
              'to' : dst_fname,
 
875
              }))
 
876
          except OSError:
 
877
            # diff command does not exist, try difflib.unified_diff()
 
878
            content = DifflibDiffContent(label1, label2, src_fname, dst_fname)
849
879
 
850
880
      # return a data item for this diff
851
881
      return _data(
864
894
        content=content,
865
895
        )
866
896
 
 
897
def _classify_diff_line(line, seen_change):
 
898
  # classify the type of line.
 
899
  first = line[:1]
 
900
  ltype = ''
 
901
  if first == '@':
 
902
    seen_change = True
 
903
    ltype = 'H'
 
904
  elif first == '-':
 
905
    if seen_change:
 
906
      ltype = 'D'
 
907
    else:
 
908
      ltype = 'F'
 
909
  elif first == '+':
 
910
    if seen_change:
 
911
      ltype = 'A'
 
912
    else:
 
913
      ltype = 'T'
 
914
  elif first == ' ':
 
915
    ltype = 'C'
 
916
  else:
 
917
    ltype = 'U'
 
918
 
 
919
  if line[-2] == '\r':
 
920
    line=line[0:-2] + '\n' # remove carriage return
 
921
 
 
922
  return line, ltype, seen_change
 
923
 
867
924
 
868
925
class DiffContent:
869
926
  "This is a generator-like object returning annotated lines of a diff."
891
948
      self.pipe = None
892
949
      raise IndexError
893
950
 
894
 
    # classify the type of line.
895
 
    first = line[:1]
896
 
    if first == '@':
897
 
      self.seen_change = True
898
 
      ltype = 'H'
899
 
    elif first == '-':
900
 
      if self.seen_change:
901
 
        ltype = 'D'
902
 
      else:
903
 
        ltype = 'F'
904
 
    elif first == '+':
905
 
      if self.seen_change:
906
 
        ltype = 'A'
907
 
      else:
908
 
        ltype = 'T'
909
 
    elif first == ' ':
910
 
      ltype = 'C'
911
 
    else:
912
 
      ltype = 'U'
913
 
 
914
 
    if line[-2] == '\r':
915
 
      line=line[0:-2] + '\n' # remove carriage return
916
 
 
917
 
    return _data(
918
 
      raw=line,
919
 
      text=line[1:-1],  # remove indicator and newline
920
 
      type=ltype,
921
 
      )
922
 
 
 
951
    line, ltype, self.seen_change = _classify_diff_line(line, self.seen_change)
 
952
    return _data(
 
953
      raw=line,
 
954
      text=line[1:-1],  # remove indicator and newline
 
955
      type=ltype,
 
956
      )
 
957
 
 
958
class DifflibDiffContent():
 
959
  "This is a generator-like object returning annotated lines of a diff."
 
960
 
 
961
  def __init__(self, label_from, label_to, from_file, to_file):
 
962
    import difflib
 
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)
 
968
 
 
969
  def __nonzero__(self):
 
970
    # we always have some items
 
971
    return True
 
972
 
 
973
  def __getitem__(self, idx):
 
974
 
 
975
    try:
 
976
      line = self.diff.next()
 
977
    except StopIteration:
 
978
      raise IndexError
 
979
 
 
980
    line, ltype, self.seen_change = _classify_diff_line(line, self.seen_change)
 
981
    return _data(
 
982
      raw=line,
 
983
      text=line[1:-1],  # remove indicator and newline
 
984
      type=ltype,
 
985
      )
923
986
 
924
987
class TextCommitRenderer:
925
988
  "This class will render the commit mail in plain text."
1238
1301
      else:
1239
1302
        exclude_paths_re = None
1240
1303
 
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)
 
1308
      else:
 
1309
        search_logmsg_re = None
 
1310
 
 
1311
      self._group_re.append((group,
 
1312
                             re.compile(for_paths),
 
1313
                             exclude_paths_re,
 
1314
                             params,
 
1315
                             search_logmsg_re))
1243
1316
 
1244
1317
    # after all the groups are done, add in the default group
1245
1318
    try:
1246
1319
      self._group_re.append((None,
1247
1320
                             re.compile(self.defaults.for_paths),
1248
1321
                             None,
1249
 
                             self._default_params))
 
1322
                             self._default_params,
 
1323
                             None))
1250
1324
    except AttributeError:
1251
1325
      # there is no self.defaults.for_paths
1252
1326
      pass
1253
1327
 
1254
 
  def which_groups(self, path):
 
1328
  def which_groups(self, path, logmsg):
1255
1329
    "Return the path's associated groups."
1256
1330
    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)
1259
1333
      if match:
1260
1334
        if exclude_pattern and exclude_pattern.match(path):
1261
1335
          continue
1262
1336
        params = repos_params.copy()
1263
1337
        params.update(match.groupdict())
1264
 
        groups.append((group, params))
 
1338
 
 
1339
        if search_logmsg_re is None:
 
1340
          groups.append((group, params))
 
1341
        else:
 
1342
          if logmsg is None:
 
1343
            logmsg = ''
 
1344
 
 
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))
 
1350
 
1265
1351
    if not groups:
1266
1352
      groups.append((None, self._default_params))
 
1353
 
1267
1354
    return groups
1268
1355
 
1269
1356