Index: tools/hook-scripts/mailer/mailer.py =================================================================== --- tools/hook-scripts/mailer/mailer.py (revision 27854) +++ tools/hook-scripts/mailer/mailer.py (working copy) @@ -1,4 +1,5 @@ #!/usr/bin/env python +# vi:set ts=2 sw=2 sts=2 et syntax=python fileencoding=utf-8: # # mailer.py: send email describing a commit # @@ -10,9 +11,9 @@ # USAGE: mailer.py commit REPOS REVISION [CONFIG-FILE] # mailer.py propchange REPOS REVISION AUTHOR REVPROPNAME [CONFIG-FILE] # mailer.py propchange2 REPOS REVISION AUTHOR REVPROPNAME ACTION \ -# [CONFIG-FILE] -# mailer.py lock REPOS AUTHOR [CONFIG-FILE] -# mailer.py unlock REPOS AUTHOR [CONFIG-FILE] +# [CONFIG-FILE] < OLD-VALUE-FILE +# mailer.py lock REPOS AUTHOR [CONFIG-FILE] < PATHS-LIST-FILE +# mailer.py unlock REPOS AUTHOR [CONFIG-FILE] < PATHS-LIST-FILE # # Using CONFIG-FILE, deliver an email describing the changes between # REV and REV-1 for the repository REPOS. @@ -48,17 +49,25 @@ import svn.repos import svn.core except ImportError: - sys.stderr.write( - "You need version %s or better of the Subversion Python bindings.\n" \ - % string.join(map(lambda x: str(x), _MIN_SVN_VERSION), '.')) + sys.stderr.write("Could not find Subversion Python bindings.\n") sys.exit(1) -if _MIN_SVN_VERSION > [svn.core.SVN_VER_MAJOR, - svn.core.SVN_VER_MINOR, - svn.core.SVN_VER_PATCH]: +_SVN_VERSION = [ + svn.core.SVN_VER_MAJOR, + svn.core.SVN_VER_MINOR, + svn.core.SVN_VER_PATCH, +] +if _SVN_VERSION < _MIN_SVN_VERSION: + """ sys.stderr.write( - "You need version %s or better of the Subversion Python bindings.\n" \ - % string.join(map(lambda x: str(x), _MIN_SVN_VERSION), '.')) + "Version %s of the Subversion Python bindings was detected, %s would be better.\n" \ + % ( + string.join(map(lambda x: str(x), _SVN_VERSION), '.'), + string.join(map(lambda x: str(x), _MIN_SVN_VERSION), '.'), + ) + ) sys.exit(1) + """ + pass SEPARATOR = '=' * 78 @@ -149,21 +158,52 @@ self.subject = "" def make_subject(self, group, params): - prefix = self.cfg.get(self.prefix_param, group, params) - if prefix: - subject = prefix + ' ' + self.subject - else: - subject = self.subject + subject_prefix = self.cfg.get(self.prefix_param, group, params) or '' + repos_rev = self.repos.rev or '' + repos_log = self.repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or '' + + # FIXME: Remove hard-coded dependency on the messenger type (Commit, + # PropChange or Lock). This should get rid of direct assignments to + # the Messenger.output.subject member variable. + # + # One way to implement this is to set %(subject_messenger)s from + # the Messenger's descendants. + # + # Better, split the subject_format configuration item into + # subject_commit_format, subject_propchange_format and subject_lock_format + # whose regular values would be + # 'r%(repos_rev)s - %(repos_commondir)s%(repos_dirs)s' + # etc. + + # Obtain the hard-coded subject line set by Messenger's descendants via + # the output member variable. + subject_messenger = self.subject or '' + subject_sep = subject_prefix and subject_messenger and ' ' or '' + subject_std = subject_prefix + subject_sep + subject_messenger + + local_params = { + 'subject_prefix': subject_prefix, + 'subject_messenger': subject_messenger, + 'repos_rev': repos_rev, + 'repos_log': repos_log, + 'subject_std': subject_std, + } + local_params.update(params) + formatted_subject = self.cfg.get('subject_format', group, local_params) + if not formatted_subject: + formatted_subject = subject_std + + formatted_subject = formatted_subject.replace('\n', ' ').strip() + try: - truncate_subject = int( - self.cfg.get('truncate_subject', group, params)) - except ValueError: + truncate_subject = int(self.cfg.get('truncate_subject', group, local_params)) + except: truncate_subject = 0 - if truncate_subject and len(subject) > truncate_subject: - subject = subject[:(truncate_subject - 3)] + "..." - return subject + if truncate_subject and len(formatted_subject) > truncate_subject: + formatted_subject = formatted_subject[:(truncate_subject - 3)] + '...' + return formatted_subject def start(self, group, params): """Override this method. @@ -185,15 +225,17 @@ Append the literal text string OUTPUT to the output representation.""" raise NotImplementedError - def run(self, cmd): + def run(self, cmd, w): """Override this method, if the default implementation is not sufficient. Execute CMD, writing the stdout produced to the output representation.""" + if w is None: + w = self.write # By default we choose to incorporate child stderr into the output pipe_ob = Popen4(cmd) buf = pipe_ob.fromchild.read(self._CHUNKSIZE) while buf: - self.write(buf) + w(buf) buf = pipe_ob.fromchild.read(self._CHUNKSIZE) # wait on the child so we don't end up with a billion zombies @@ -204,6 +246,9 @@ def __init__(self, cfg, repos, prefix_param): OutputBase.__init__(self, cfg, repos, prefix_param) + def write_nop(self, output): + pass + def start(self, group, params): # whitespace (or another character) separated list of addresses # which must be split into a clean list @@ -217,6 +262,8 @@ filter(None, string.split(to_addr_in[3:], to_addr_in[1])) else: self.to_addrs = filter(None, string.split(to_addr_in)) + if not self.to_addrs: + self.write = self.write_nop self.from_addr = self.cfg.get('from_addr', group, params) \ or self.repos.author or 'no_author' # if the from_addr (also) starts with '[.]' (may happen if one @@ -255,6 +302,8 @@ def start(self, group, params): MailedOutput.start(self, group, params) + if not self.to_addrs: + return self.buffer = cStringIO.StringIO() self.write = self.buffer.write @@ -262,6 +311,8 @@ self.write(self.mail_headers(group, params)) def finish(self): + if not self.to_addrs: + return server = smtplib.SMTP(self.cfg.general.smtp_hostname) if self.cfg.is_set('general.smtp_username'): server.login(self.cfg.general.smtp_username, @@ -270,16 +321,19 @@ server.quit() -class StandardOutput(OutputBase): +class StandardOutput(MailedOutput): "Print the commit message to stdout." - def __init__(self, cfg, repos, prefix_param): - OutputBase.__init__(self, cfg, repos, prefix_param) + def start(self, group, params): + MailedOutput.start(self, group, params) + if not self.to_addrs: + return + self.write = sys.stdout.write - def start(self, group, params): - self.write("Group: " + (group or "defaults") + "\n") - self.write("Subject: " + self.make_subject(group, params) + "\n\n") + self.write('Group: ' + (group or "defaults") + '\n') + self.write('To: ' + string.join(self.to_addrs, ', ') + '\n') + self.write('Subject: ' + self.make_subject(group, params) + '\n\n') def finish(self): pass @@ -296,12 +350,15 @@ def start(self, group, params): MailedOutput.start(self, group, params) + if not self.to_addrs: + return ### gotta fix this. this is pretty specific to sendmail and qmail's ### mailwrapper program. should be able to use option param substitution cmd = self.cmd + [ '-f', self.from_addr ] + self.to_addrs # construct the pipe for talking to the mailer + # print "cmd = " + str(cmd) self.pipe = Popen3(cmd) self.write = self.pipe.tochild.write @@ -312,6 +369,8 @@ self.write(self.mail_headers(group, params)) def finish(self): + if not self.to_addrs: + return # signal that we're done sending content self.pipe.tochild.close() @@ -452,32 +511,40 @@ actions = { 'A': 'added', 'M': 'modified', 'D': 'deleted' } for (group, param_tuple), params in self.groups.items(): self.output.start(group, params) - self.output.write('Author: %s\n' - 'Revision: %s\n' - 'Property Name: %s\n' - 'Action: %s\n' - '\n' - % (self.author, self.repos.rev, self.propname, + body_header = 'Author: %s\n' \ + 'Revision: %s\n' \ + 'Property Name: %s\n' \ + 'Action: %s\n' \ + '\n' \ + % (self.author, self.repos.rev, self.propname, \ actions.get(self.action, 'Unknown (\'%s\')' \ - % self.action))) + % self.action)) + string_output = cStringIO.StringIO() + w = string_output.write if self.action == 'A' or not actions.has_key(self.action): - self.output.write('Property value:\n') - propvalue = self.repos.get_rev_prop(self.propname) - self.output.write(propvalue) + w('\nProperty value:\n') + propvalue = self.repos.get_rev_prop(self.propname) or '' + w(propvalue) elif self.action == 'M': - self.output.write('Property diff:\n') + w('\nProperty diff:\n') tempfile1 = NamedTemporaryFile() tempfile1.write(sys.stdin.read()) tempfile1.flush() tempfile2 = NamedTemporaryFile() - tempfile2.write(self.repos.get_rev_prop(self.propname)) + tempfile2.write(self.repos.get_rev_prop(self.propname) or '') tempfile2.flush() self.output.run(self.cfg.get_diff_cmd(group, { 'label_from' : 'old property value', 'label_to' : 'new property value', 'from' : tempfile1.name, 'to' : tempfile2.name, - })) + }), w) + body_summary = string_output.getvalue() + self.output.write( + format_body( + self.cfg, group, params, + self.author, '', self.repos.rev, '', + body_header, body_summary, '', '')) self.output.finish() @@ -562,20 +629,58 @@ for (group, param_tuple), (params, paths) in self.groups.items(): self.output.start(group, params) - self.output.write('Author: %s\n' - '%s paths:\n' % - (self.author, self.do_lock and 'Locked' or 'Unlocked')) + body_header = 'Author: %s\n%s.\n' % \ + (self.author, self.do_lock and 'Locked' or 'Unlocked') + body_summary = '\nPaths:\n' self.dirlist.sort() for dir in self.dirlist: - self.output.write(' %s\n\n' % dir) + body_summary += ' %s\n' % dir + body_log = '\nComment:\n' if self.do_lock: - self.output.write('Comment:\n%s\n' % (self.lock.comment or '')) + try: + body_log += '%s\n' % (self.lock.comment or '') + except AttributeError: + # Backward compatibility where lock doesn't have comment. + pass + self.output.write( + format_body( + self.cfg, group, params, + self.author, '', '', '', + body_header, body_summary, body_log, '')) self.output.finish() +def format_body( + cfg, group, params, + repos_author, repos_date, repos_rev, repos_log, + body_header, body_summary, body_log, body_diff): + + body_std = body_header + body_log + body_summary + body_diff + + local_params = { + 'repos_author': repos_author, + 'repos_date': repos_date, + 'repos_rev': repos_rev, + 'repos_log': repos_log, + 'body_header': body_header, + 'body_summary': body_summary, + 'body_log': body_log, + 'body_diff': body_diff, + 'body_std': body_std, + } + local_params.update(params) + + formatted_body = cfg.get('body_format', group, local_params) + + if not formatted_body: + formatted_body = body_std + + return formatted_body + + class DiffSelections: def __init__(self, cfg, group, params): self.add = False @@ -694,19 +799,30 @@ diffs=DiffGenerator(changelist, paths, True, cfg, repos, date, group, params, diffsels, diffurls, pool), other_diffs=other_diffs, + cfg=cfg, + group=group, + params=params, ) renderer.render(data) +if _SVN_VERSION < _MIN_SVN_VERSION: + _SELECTION = { + 'A': lambda change: change.added, + 'R': lambda change: False, + 'D': lambda change: not change.added and change.path is None, + 'M': lambda change: not change.added and change.path is not None, + } +else: + _SELECTION = { + 'A': lambda change: change.action == svn.repos.CHANGE_ACTION_ADD, + 'R': lambda change: change.action == svn.repos.CHANGE_ACTION_REPLACE, + 'D': lambda change: change.action == svn.repos.CHANGE_ACTION_DELETE, + 'M': lambda change: change.action == svn.repos.CHANGE_ACTION_MODIFY, + } + def generate_list(changekind, changelist, paths, in_paths): - if changekind == 'A': - selection = lambda change: change.action == svn.repos.CHANGE_ACTION_ADD - elif changekind == 'R': - selection = lambda change: change.action == svn.repos.CHANGE_ACTION_REPLACE - elif changekind == 'D': - selection = lambda change: change.action == svn.repos.CHANGE_ACTION_DELETE - elif changekind == 'M': - selection = lambda change: change.action == svn.repos.CHANGE_ACTION_MODIFY + selection = _SELECTION[changekind] items = [ ] for path, change in changelist: @@ -716,8 +832,7 @@ is_dir=change.item_kind == svn.core.svn_node_dir, props_changed=change.prop_changes, text_changed=change.text_changed, - copied=(change.action == svn.repos.CHANGE_ACTION_ADD \ - or change.action == svn.repos.CHANGE_ACTION_REPLACE) \ + copied=(_SELECTION['A'](change) or _SELECTION['R'](change)) \ and change.base_path, base_path=remove_leading_slashes(change.base_path), base_rev=change.base_rev, @@ -789,7 +904,7 @@ # figure out if/how to generate a diff base_path = remove_leading_slashes(change.base_path) - if change.action == svn.repos.CHANGE_ACTION_DELETE: + if _SELECTION['D'](change): # it was delete. kind = 'D' @@ -805,8 +920,8 @@ label2 = '/dev/null\t00:00:00 1970\t(deleted)' singular = True - elif change.action == svn.repos.CHANGE_ACTION_ADD \ - or change.action == svn.repos.CHANGE_ACTION_REPLACE: + elif _SELECTION['A'](change) \ + or _SELECTION['R'](change): if base_path and (change.base_rev != -1): # any diff of interest? @@ -973,47 +1088,59 @@ def render(self, data): "Render the commit defined by 'data'." - w = self.output.write + body_header = 'Author: %s\nDate: %s\nNew Revision: %s\n' \ + % (data.author, data.date, data.rev) - w('Author: %s\nDate: %s\nNew Revision: %s\n' % (data.author, - data.date, - data.rev)) + # print summary sections + string_output = cStringIO.StringIO() + w = string_output.write if data.commit_url: w('URL: %s\n\n' % data.commit_url) else: w('\n') - w('Log:\n%s\n\n' % data.log.strip()) + body_log = '\nLog:\n%s\n' % data.log.strip() # print summary sections - self._render_list('Added', data.added_data) - self._render_list('Replaced', data.replaced_data) - self._render_list('Deleted', data.deleted_data) - self._render_list('Modified', data.modified_data) + self._render_list(w, 'Added', data.added_data) + self._render_list(w, 'Replaced', data.replaced_data) + self._render_list(w, 'Deleted', data.deleted_data) + self._render_list(w, 'Modified', data.modified_data) if data.other_added_data or data.other_replaced_data \ or data.other_deleted_data or data.other_modified_data: if data.show_nonmatching_paths: w('\nChanges in other areas also in this revision:\n') - self._render_list('Added', data.other_added_data) - self._render_list('Replaced', data.other_replaced_data) - self._render_list('Deleted', data.other_deleted_data) - self._render_list('Modified', data.other_modified_data) + self._render_list(w, 'Added', data.other_added_data) + self._render_list(w, 'Replaced', data.other_replaced_data) + self._render_list(w, 'Deleted', data.other_deleted_data) + self._render_list(w, 'Modified', data.other_modified_data) else: w('and changes in other areas\n') + body_summary = string_output.getvalue() - self._render_diffs(data.diffs, '') + string_output = cStringIO.StringIO() + w = string_output.write + + self._render_diffs(w, data.diffs, '') if data.other_diffs: - self._render_diffs(data.other_diffs, + self._render_diffs(w, data.other_diffs, '\nDiffs of changes in other areas also' ' in this revision:\n') - def _render_list(self, header, data_list): + body_diff = string_output.getvalue() + + self.output.write( + format_body( + data.cfg, data.group, data.params, + data.author, data.date, data.rev, data.log, + body_header, body_summary, body_log, body_diff)) + + def _render_list(self, w, header, data_list): if not data_list: return - w = self.output.write w(header + ':\n') for d in data_list: if d.is_dir: @@ -1038,12 +1165,11 @@ w(' - copied%s from r%d, %s%s\n' % (text, d.base_rev, d.base_path, is_dir)) - def _render_diffs(self, diffs, section_header): + def _render_diffs(self, w, diffs, section_header): """Render diffs. Write the SECTION_HEADER if there are actually any diffs to render.""" if not diffs: return - w = self.output.write section_header_printed = False for diff in diffs: @@ -1084,7 +1210,6 @@ for line in diff.content: w(line.raw) - class Repository: "Hold roots and other information about the repository." @@ -1180,9 +1305,16 @@ if value is None: value = getattr(self.defaults, option, '') + # Expand encoded new lines. + if value is not None: + value = value.replace(r'\n', '\n') + # parameterize it if params is not None: - value = value % params + try: + value = value % params + except KeyError: + pass # apply any mapper mapper = getattr(self.maps, option, None) @@ -1342,9 +1474,10 @@ sys.stderr.write( """USAGE: %s commit REPOS REVISION [CONFIG-FILE] %s propchange REPOS REVISION AUTHOR REVPROPNAME [CONFIG-FILE] - %s propchange2 REPOS REVISION AUTHOR REVPROPNAME ACTION [CONFIG-FILE] - %s lock REPOS AUTHOR [CONFIG-FILE] - %s unlock REPOS AUTHOR [CONFIG-FILE] + %s propchange2 REPOS REVISION AUTHOR REVPROPNAME ACTION \\ + [CONFIG-FILE] < OLD-VALUE-FILE + %s lock REPOS AUTHOR [CONFIG-FILE] < PATHS-LIST-FILE + %s unlock REPOS AUTHOR [CONFIG-FILE] < PATHS-LIST-FILE If no CONFIG-FILE is provided, the script will first search for a mailer.conf file in REPOS/conf/. Failing that, it will search the directory in which @@ -1372,8 +1505,13 @@ usage() cmd = sys.argv[1] - repos_dir = svn.core.svn_path_canonicalize(sys.argv[2]) try: + repos_dir = svn.core.svn_path_canonicalize(sys.argv[2]) + except AttributeError: + # backward compatibility for svn.core without svn_path_canonicalize + repos_dir = sys.argv[2] + + try: expected_args = cmd_list[cmd] except KeyError: usage() Index: tools/hook-scripts/mailer/tests/mailer-t1.output =================================================================== --- tools/hook-scripts/mailer/tests/mailer-t1.output (revision 27854) +++ tools/hook-scripts/mailer/tests/mailer-t1.output (working copy) @@ -1,4 +1,5 @@ Group: file plus other areas +To: invalid@example.com Subject: r1 - dir1 dir2 Author: mailer test @@ -65,6 +66,7 @@ @@ -0,0 +1 @@ +file6 Group: All +To: invalid@example.com Subject: r1 - dir1 dir2 Author: mailer test @@ -126,6 +128,7 @@ @@ -0,0 +1 @@ +file2 Group: file +To: invalid@example.com Subject: r1 - dir1 dir2 Author: mailer test @@ -153,6 +156,7 @@ @@ -0,0 +1 @@ +file2 Group: file plus other areas +To: invalid@example.com Subject: r2 - dir1 dir2 Author: mailer test @@ -189,6 +193,7 @@ file5 +change C2 Group: All +To: invalid@example.com Subject: r2 - dir1 dir2 Author: mailer test @@ -220,6 +225,7 @@ file2 +change C1 Group: file +To: invalid@example.com Subject: r2 - dir1 dir2 Author: mailer test @@ -241,6 +247,7 @@ file2 +change C1 Group: All +To: invalid@example.com Subject: r3 - dir2 dir3 Author: mailer test @@ -263,6 +270,7 @@ @@ -0,0 +1 @@ +file1 Group: All +To: invalid@example.com Subject: r4 - dir3 Author: mailer test @@ -284,6 +292,7 @@ file1 +change C3 Group: file plus other areas +To: invalid@example.com Subject: r5 - dir1 dir3 Author: mailer test @@ -301,6 +310,7 @@ dir1/ (props changed) dir3/ (props changed) Group: All +To: invalid@example.com Subject: r5 - dir1 dir3 Author: mailer test @@ -315,6 +325,7 @@ dir3/ (props changed) file2 (props changed) Group: file +To: invalid@example.com Subject: r5 - dir1 dir3 Author: mailer test @@ -327,6 +338,7 @@ Modified: file2 (props changed) Group: file plus other areas +To: invalid@example.com Subject: r6 - dir1 dir4 Author: mailer test @@ -362,6 +374,7 @@ file3 +change C4 Group: All +To: invalid@example.com Subject: r6 - dir1 dir4 Author: mailer test @@ -392,6 +405,7 @@ @@ -0,0 +1 @@ +file9 Group: file +To: invalid@example.com Subject: r6 - dir1 dir4 Author: mailer test @@ -411,6 +425,7 @@ @@ -0,0 +1 @@ +file9 Group: file plus other areas +To: invalid@example.com Subject: r7 - dir1 dir2 dir3 dir3/dir5 Author: mailer test @@ -457,6 +472,7 @@ file3 +change C5 Group: All +To: invalid@example.com Subject: r7 - dir1 dir2 dir3 dir3/dir5 Author: mailer test @@ -498,6 +514,7 @@ -file2 -change C1 Group: file +To: invalid@example.com Subject: r7 - dir1 dir2 dir3 dir3/dir5 Author: mailer test @@ -518,6 +535,7 @@ -file2 -change C1 Group: All +To: invalid@example.com Subject: r8 - in dir6: . dir5 Author: mailer test @@ -554,7 +572,8 @@ file4 +change C6 Group: file plus other areas -Subject: r9 - +To: invalid@example.com +Subject: r9 - Author: mailer test Date: Mon Sep 10 00:00:00 2001 @@ -572,7 +591,8 @@ ============================================================================== Binary file. No diff available. Group: All -Subject: r9 - +To: invalid@example.com +Subject: r9 - Author: mailer test Date: Mon Sep 10 00:00:00 2001 @@ -590,7 +610,8 @@ ============================================================================== Binary file. No diff available. Group: file -Subject: r9 - +To: invalid@example.com +Subject: r9 - Author: mailer test Date: Mon Sep 10 00:00:00 2001 @@ -608,7 +629,8 @@ ============================================================================== Binary file. No diff available. Group: file plus other areas -Subject: r10 - +To: invalid@example.com +Subject: r10 - Author: mailer test Date: Mon Sep 10 02:46:40 2001 @@ -625,7 +647,8 @@ ============================================================================== Binary file (source and/or target). No diff available. Group: All -Subject: r10 - +To: invalid@example.com +Subject: r10 - Author: mailer test Date: Mon Sep 10 02:46:40 2001 @@ -642,7 +665,8 @@ ============================================================================== Binary file (source and/or target). No diff available. Group: file -Subject: r10 - +To: invalid@example.com +Subject: r10 - Author: mailer test Date: Mon Sep 10 02:46:40 2001 Index: tools/hook-scripts/mailer/mock-sendmail =================================================================== --- tools/hook-scripts/mailer/mock-sendmail (revision 0) +++ tools/hook-scripts/mailer/mock-sendmail (revision 0) @@ -0,0 +1,9 @@ +#! /bin/bash +exec 1>&2 +for p ; do + echo Param == $p == +done +echo Data. +cat +echo End of data. + Index: tools/hook-scripts/mailer/mailer.conf.example =================================================================== --- tools/hook-scripts/mailer/mailer.conf.example (revision 27854) +++ tools/hook-scripts/mailer/mailer.conf.example (working copy) @@ -20,6 +20,9 @@ # line, and the message piped into it. #mail_command = /usr/sbin/sendmail +# Mock sendmail to see the exact messages, their envelopes and headers. +#mail_command = ./mock-sendmail + # This option specifies the hostname for delivery via SMTP. #smtp_hostname = localhost @@ -138,22 +141,52 @@ [defaults] +for_repos = .*/(?P.*)$ +# Avoid sending messages in addition to user-defined groups. +for_paths = ^$ + # This is not passed to the shell, so do not use shell metacharacters. # The command is split around whitespace, so if you want to include # whitespace in the command, then ### something ###. diff = /usr/bin/diff -u -L %(label_from)s -L %(label_to)s %(from)s %(to)s +# The format of the subject line. May include the following: +# %(subject_prefix)s = one of xxx_subject_prefix + ' ' +# %(subject_messenger)s = change-type-specific, such as this pseudo +# format line for commits: +# r%(repos_rev)s - %(repos_commondir)s%(repos_dirs)s +# %(subject_std)s = %(subject_prefix)s%(subject_messenger)s +# %(repos_rev)s +# %(repos_log)s +subject_format = %(subject_prefix)s%(repos_log)s + +# The format of the body content. +# May include the following parameters: +# %(body_header)s = Author: %(repos_author)s +# Date: %(repos_date)s +# New Revision: %(repos_rev)s\n\n +# %(body_summary)s +# %(body_log)s = \nLog:\n%(repos_log)s\n +# %(body_diff)s +# %(body_std)s = %(body_header)s%(body_summary)s%(body_log)s%(body_diff)s +# %(repos_author)s +# %(repos_date)s +# %(repos_rev)s +# %(repos_log)s +body_format = http://example.com/viewvc/%(repo)s?view=rev&revision=%(repos_rev)s + %(body_header)s%(body_log)s%(body_summary)s%(body_diff)s + # The default prefix for the Subject: header for commits. -commit_subject_prefix = +commit_subject_prefix = [%(project)s changes] # The default prefix for the Subject: header for propchanges. -propchange_subject_prefix = +propchange_subject_prefix = [%(project)s changes] # The default prefix for the Subject: header for locks. -lock_subject_prefix = +lock_subject_prefix = [%(project)s changes] # The default prefix for the Subject: header for unlocks. -unlock_subject_prefix = +unlock_subject_prefix = [%(project)s changes] # The default From: address for messages. If the from_addr is not @@ -164,7 +197,7 @@ # 'no_author' is used. You can specify a default from_addr here and # if you want to have a particular for_repos group use the author as # the from address, you can use "from_addr =". -from_addr = invalid@example.com +from_addr = %(author)s@example.com # The default To: addresses for message. One or more addresses, # separated by whitespace (no commas). @@ -241,7 +274,7 @@ # Subject line length limit. The generated subject line will be truncated # and terminated with "...", to remain within the specified maximum length. # Set to 0 to turn off. -#truncate_subject = 200 +truncate_subject = 60 # -------------------------------------------------------------------------- @@ -329,3 +362,39 @@ # for_repos = /home/(?P[^/]*)/repos # to_addr = %(who)s@example.com # +[repos1] +for_repos = .*/(repos1)$ +exclude_paths = ^(vendor)(/.*|)$ +for_paths = ^(?P[^/]*)(/.*|)$ + +[repos1-vendor] +for_repos = .*/(repos1)$ +for_paths = ^(?Pvendor)(/.*|)$ +generate_diffs = copy modify + +[repos2-dir1] +for_repos = .*/(repos2)$ +for_paths = ^(?Pdir1)(/.*|)$ +# Skip notification on changes in dir1. +to_addr = + +[repos2-dir2] +for_repos = .*/(repos2)$ +for_paths = ^(?Pdir2|dir3)(/.*|)$ +# Notify a limited list of recipients on changes in dir2 and dir3. +to_addr = recipient1 recipient2 + +[other] +for_repos = .*/(repos3|repos4)$ +for_paths = ^(?P[^/]*)(/.*|)$ + +[sandbox-selected] +for_repos = .*/(sandbox)$ +exclude_paths = ^(test_exclude)/ +for_paths = ^(?P[^/]*)(/.*|)$ + +[sandbox-excluded] +for_repos = .*/(sandbox)$ +for_paths = ^(?Ptest_exclude)(/.*|)$ +to_addr = +