Table Of Contents
General Comments
The svnmailer is an extensible subversion commit notification tool. However, its purpose in the first place is to create human readable commit messages. In order to accomplish this, the content may be recoded to fit the requirements of the MIME standard.
That means that you always get valid and readable mails,
but you SHOULD NOT
expect to be able to copy the diffs from the generated mails and
apply them with the patch
program.
Use svn diff
or viewcvs diffs for
such tasks. The svnmailer is able to generate the proper viewcvs urls
and place them quite handy near the diffs in the notification mails.
Installation
Before installing the svnmailer package make sure, that you meet the following requirements:
- Python 2.3 or later
- A POSIX compatible platform (Win32 is supported but not fully tested yet)
- The subversion bindings for python
The svnmailer is a pure python package, which is packed using distutils. So the installation on your system is fairly simple. First download the package and make sure you've checked the integrity of the downloaded file. Use the supplied PGP signature and/or the MD5 hash for this task.
After unpacking the archive file change into the
svnmailer-0.9.9
directory and follow the typical
python procedure:
$ bzip2 -cd svnmailer-0.9.9.tar.bz2 | tar -xf -
$ cd svnmailer-0.9.9
$ python setup.py install
Now there should be two things installed, the svn-mailer
command line script and svnmailer package itself. The package
is copied to the "proper" location, where python finds it by
default. The location of the script depends on the OS and the
python installation. For example, on linux it typically installs into
/usr/bin
or /usr/local/bin
. For
customizations please refer to the related python
documentation.
The next step is to create a configuration file. After you've done that, you can configure your repository to let the svnmailer do its work.
Config File Design
In short: the configuration file controls, who gets the notifications for which path in which repository. Further it defines the basic script parameters like how to send the notification mail, which diff program to use (if any) and so on. It it supposed to be compatible to the config of the original mailer.py script.
Syntactical Elements
The file is a plain text file in an INI like format as defined by the standard python ConfigParser module. Basically it consists of several sections, that are started with a line containing:
[section name]
and finished at the next [section]
or the end of the
file. Values are defined that way:
name = value
# or
name: value
As you might have guessed, comments are preceded by the hash character
(#
). Empty lines (or lines containing only whitespaces) are
ignored. Note that section headings, comments and value definitions have
to start at the first column, because the configuration parser treats
(non-empty) lines starting with spaces or tabs as continuations of the
previous line (like in mail headers).
Semantics
The sections in your config file are processed by the svnmailer as
follows: The [general]
section
contains the basic script parameters. The [defaults]
section contains default values for the group sections. All values
that are not defined in a selected notification group are taken from
the defaults. [defaults]
is optional. All other sections define notification groups.
The names of these sections don't care (except for debugging purposes).
They just have to be unique within the config and may not be named
defaults
or general
, of course. if there
is no separate group configuration, the defaults will be applied.
So a minimal config is:
[general]
If you call the svnmailer with this config, it will generate diffs
for every possible action at any path for any supplied repository and
write a notification message to stdout
.
Group Selection
When the svnmailer is called, it reads the config file and selects
all groups, which should be notified of that particular event (commit
or revprop change). Which groups are selected, is determined by the for_repos
, for_paths
, exclude_paths
and ignore_if_other_matches
options. Since the svnmailer tries to minimize the number of
notification mails, each of these selected groups gets one mail at
maximum per event. Furthermore, if the notifications generated for
different selected groups are detected to be equal, these groups are
merged and just one mail is sent to all of those groups. Currently
this detection compares the list of modified paths (that were matched
by each group) and several configuration options, which are in
particular: commit_subject_prefix
, propchange_subject_prefix
, generate_diffs
and viewcvs_base_url
.
Note that there's a subtle difference to the original mailer
script. If there are group configurations defined, the svnmailer will
never consider the [defaults]
section as an additional
group to be notified.
[general] Configuration Section
Option Name | Type | Description |
---|---|---|
config_charset |
string | The character encoding of the config file |
diff_command |
command line | The external diff program to use. |
sendmail_command |
command line | The sendmail compatible command line template |
smtp_host |
string | The SMTP host[:port] to
use |
smtp_user |
quoted literal | The user used for SMTP authentication |
smtp_pass |
quoted literal | The password used for SMTP authentication |
debug_all_mails_to |
mail addresses | Mails should go only to these fixed addresses |
Though the [general]
section defines such basic
parameters, it may be finally empty, because there are more or less
useful defaults given for each option. However, to make sure, that
you didn't forget it, the svnmailer requires at least the section
heading to be present in the config.
The different options play together as follows:
- If the
diff_command
option is given and not empty, it is used to generate the diff output. Otherwise an internal differ is used. The internal differ always generates the unified diff format. - If the
sendmail_command
option is given, it is used to send the mail. - If the
smtp_host
option is given andsendmail_command
is not given or empty, the former is used to open an smtp session to the specified host and deliver the mail via SMTP. If the server requires authentication, you have to supply bothsmtp_user
andsmtp_pass
. However, if the server doesn't support authentication, you may not supply thesmtp_user
option (alternatively just leave it empty). - If neither
sendmail_command
norsmtp_host
is specified, the notification message is written to stdout (with a small header, which configuration groups were selected). This can be used for debugging purposes.
config_charset
The config_charset
option defines, how
the svnmailer should interpret the (bytes read as) option values in
the config file. It defaults to us-ascii
, which means,
normally you can leave it just out. But note that there are
exceptions. The options of type "quoted literal" are not
charset decoded, but taken literally (possibly unquoted, however).
# Example
# =======
[general]
config_charset = iso-8859-1
diff_command
The diff_command
option defines, that
you want to use an external diff program (instead of python's difflib
module), where to find and how to call it. The option value is
a template for the command line to call. The program has to write
the diff information to stdout
. Stdout
and
stderr
are caught by the svnmailer and dumped into
the mail.
As said, the value describes a template. There is a fixed number of
substitutions defined for the diff_command
option, in
particular: label_from
, label_to
,
from
and to
. from
and
to
define the actual files to process (typically some
scrambled temporary file names). label_from
and
label_to
define, how the files should be labeled by
the diff program. These substitutions are written as
%(name)s
and replaced by the svnmailer script.
If you want a literal percent character somewhere in the command line,
you have to duplicate it (i.e., write %%
instead).
The command line template is split on spaces or tabs to separate the
different arguments. If you want to have an argument contain such space
characters, you have to enclose it in double quotes ("
).
Further if you want such a quoted argument to contain a double quote
character, you have to escape it with a backslash (i.e., write
\"
instead). To complete the escaping mechanism, you have
to duplicate backslashes inside a quoted argument. The
surrounding quotes and the backslash escape characters are stripped
by the svnmailer to get the final argument string.
Note that on POSIX systems no shell is called to execute the
program, so shell metacharacters are not interpreted. However, on
Win32 the shell (cmd.exe
& Co.) is called,
but the arguments are properly escaped.
For compatibility and convenience the diff_command
option can also be written as diff
.
# Example for GNU diff
# ====================
[general]
# (note that the following is all on one line)
diff_command = /usr/bin/diff -u -L %(label_from)s -L %(label_to)s %(from)s %(to)s
sendmail_command
The sendmail_command
option defines the
command line template of the program that should be called for sending
a mail. The program should expect the mail body on stdin
.
The stdout
channel of the program is closed by the
svnmailer, but stderr
is passed through the caller of
the svnmailer.
In contrast to
diff_command
there are no substitutions made on the
arguments. Instead the final command line is constructed as follows:
- The command template is split into its arguments following the
rules described at
diff_command
. - To specifiy the mail sender the arguments
-f
and the sender address is attached to the argument list - The argument list is further extended with all recipient addresses.
This calling convention is compatible to, for example, sendmail or qmail's sendmail wrapper, hence the name sendmail_command.
As with diff_command
no shell metacharacters are
interpreted.
For compatibility reasons the sendmail_command
option
can also be written as mail_command
.
# Example
# =======
[general]
sendmail_command = /usr/sbin/sendmail
smtp_host
The smtp_host
option defines the SMTP
server to connect in order to send a mail. This option is ignored if
sendmail_command
is defined and not empty. The option value is a hostname, optionally
followed by a colon and a port. If the server supports
authentication, you can supply the required credentials via the smtp_user
and
smtp_pass
options.
# Example
# =======
[general]
smtp_host = mail.example.org
# or with port
smtp_host = mail.example.org:25
smtp_user and smtp_pass
These two options are only used if the smtp_host
is used by the
svnmailer. They define the credentials to be used in the smtp
session when attemting to send mail. If you supply
smtp_user
, you have to define smtp_pass
as
well. However, svnmailer just checks for the presence of
smtp_user
to know, if any credentials should be used.
The utilized smtp library
supports the CRAM-MD5
, PLAIN
and
LOGIN
authentication mechanisms.
Because of the nature of those two options, the values are not
considered to be charset encoded. They are sent literally to the
smtp server. In order to make sure, that spaces and other possibly
weird characters are taken literally, you can enclose the actual
string in double quotes ("
). For double quotes and
backslashes inside the quoted string apply the same
rules as for command line arguments. Have a look at the description
of diff_command
for
details. Of course, the surrounding quotes and backslash escape
characters are stripped before submitting the string to the SMTP
server.
# Example
# =======
[general]
smtp_host = mail.example.org
smtp_user = mysmtpuser
smtp_pass = mysmtppass
debug_all_mails_to
This one is a real debugging option. It specifies a fixed list of
mail addresses, where all notification mails should be sent to --
regardless of the to_addr
templates of the selected groups. The addresses of the overridden
recipients are sent along with the mail using the
X-Supposed-Recipients
header.
# Example
# =======
[general]
debug_all_mails_to = svnadmin@example.org
Group Configuration Sections
Option Name | Type | Description |
---|---|---|
for_repos |
regex | Matches the repository file path |
for_paths |
regex | Matches the virtual path inside a/the repository |
exclude_paths |
regex | Excludes paths that might be matched with
for_paths |
ignore_if_other_matches |
boolean | Determines, whether the group should be ignored, if any other group matches this path |
commit_subject_prefix |
string | The mail subject prefix for normal commits |
propchange_subject_prefix |
string | The mail subject prefix for revision property notifications |
from_addr |
template | The sender addresses |
to_addr |
template | The receiver addresses |
reply_to_addr |
template | The reply-to address |
generate_diffs |
token list | The list of actions, which generate diffs |
viewcvs_base_url |
string | Base URL of the viewcvs installation |
The options described here are all valid both in group configurations
and in the [defaults]
section. If a option in a normal group
configuration is missing, its value is taken from [defaults]
.
If there is nothing defined, a hardcoded default is applied.
for_repos
The for_repos
option defines a regular expression,
which is used to match against the file path of repository, for
example /var/svn/my-repository
. The file-path, which is
matched against is guaranteed to not have a directory
separator at the end (slash or backslash). Note that the regular
expression always matches from the beginning of the path, so your regex
typically will begin with .*
. This is, because the
svnmailer uses the re.match
function - see the python
docs for further information.
If the for_repos
option is not defined or empty, the
particular group matches for any repository (which is the default).
Named matches of this group are stored for later substitutions.
In the following example the group "sample group" will be selected
only if the script is called for the "public" repository (e.g.
/var/svn/repositories/public
):
# Example
# =======
[sample group]
for_repos = .*/public$
for_paths
The for_paths
option defines a regular expression,
which is used to match against one of the modified paths stored in the
repository, but only if the group was preselected by repository (see for_repos
). If the
path matched against is a directory, it is guaranteed to end with a
slash, so that matching by directory paths results in more simple
regular expressions. As with for_repos
, the match always
starts at the beginning of the path, but without a leading slash.
If the for_paths
option is not defined or empty, the
particular group matches for any path inside the repository (which
is the default). Named matches of this group are stored for later substitutions.
In the following example the group "sample group" will be selected
only if the script is called for the "public" repository (e.g.
/var/svn/repositories/public
) and everything under the
/site/
directory (e.g. /site/images/foo.gif
,
but not for /site-tools/buildsite.sh
):
# Example
# =======
[sample group]
for_repos = .*/public$
for_paths = site/
exclude_paths
Since regular expressions usually match positive, it's from time to
time helpful (and better readable) to exclude substrings with a
separate match. The exclude_paths
option
exists for that purpose. It matches exactly like
for_paths
, but the group is selected only if the supplied
regex does not match (and has been preselected by for_repos
and
for_paths
).
If the exclude_paths
option is not defined or empty,
nothing will be excluded (which is the default). Of course, named
groups of the match will not be stored for substitution, because
the group is not selected, if there is a match of
exclude_paths
.
In the following example the group "sample group 1" will be selected
only if the script is called for the "public" repository (e.g.
/var/svn/repositories/public
) and everything under the
/site/
directory (e.g. /site/images/foo.gif
),
but not for stuff under /site/tools/
. For every
change under the site/tools/
directory the group
"sample group 2" will be notified:
# Example
# =======
[defaults]
for_repos = .*/public$
[sample group 1]
for_paths = site/
exclude_paths = site/tools/
[sample group 2]
for_paths = site/tools/
Note that if the exclude_paths
option was not given,
every change under site/tools/
would generate a
notification for both groups.
ignore_if_other_matches
Consider a main project, which consists of several subprojects. Every subproject has its own notification group:
# Example
# =======
[defaults]
for_repos = .*/public$
[main project]
# consists of main1/ .. mainn/ and sub1/ ... subn/
# sub1 ... n get their own notification, the main project should
# be notified only for stuff other than sub?/
for_paths = project/
exclude_paths = project/(sub1|sub2|...|subn)/
[sub 1]
for_paths = project/sub1/
# :
[sub 10]
for_paths = project/sub10/
The exclude_paths
option could be matched easier, if the
sub projects really would be named
subdigit
. But usually this is not the case. As
you see, maintaining the exclude_paths
regex grows to a
nightmare the more projects are added. The
ignore_if_other_matches
option is supposed to
help out of this ugly situation. If set to a positive value (e.g.
yes
), the group will not be selected for the matched path
if there are any other groups that match the same path / repository.
The above config could be rewritten as:
# Example
# =======
[defaults]
for_repos = .*/public$
[main project]
for_paths = project/
ignore_if_other_matches = yes
[sub 1]
for_paths = project/sub1/
# :
[sub 10]
for_paths = project/sub10/
Note that there is a border case. If you use this feature for more than one group, it can happen, that finally the list of selected groups per path consists only of more than one ignorable groups. Theoretically these would unselect each other. Practically all those groups are selected, so that the notification is not lost.
The "boolean" values accepted by this option are yes
,
on
, true
and 1
for the "true"
case and no
, off
, false
,
0
, none
and the empty string for the
"false" case. The default is false.
For compatibility reasons, convenience and better readability
this option can also be written as suppress_if_match
or fallback
.
commit_subject_prefix and propchange_subject_prefix
These options define the subject prefix of the generated mails
depending on the described event. If a string is supplied, it's
included with a space between the prefix and actual the subject
line. commit_subject_prefix
defines the prefix
for normal subversion commits (files, directories and versioned
properties). propchange_subject_prefix
defines the subject prefix for unversioned property change notifications. The default prefixes
are empty.
# Example
# =======
[defaults]
commit_subject_prefix = svn commit:
propchange_subject_prefix = svn revpropchange:
from_addr, to_addr and reply_to_addr
from_addr
, to_addr
and reply_to_addr
define address templates for
the mails to be sent. Both from_addr
and
to_addr
accept space or tab separated lists of address
templates, while reply_to_addr
takes just one address. The
semantics should be quite clear: from_addr
defines the sender
addresses (but usually just one), to_addr
the recipient
addresses and reply_to_addr
the address, where answers to
the commit mail should be sent to. If groups are merged during the
selection process, there can be any number of senders, receivers and
even reply-to addresses in the mail (which conforms to RFC 2822, if you
care about such things). Duplicates in the address lists are filtered
away. In the case of more than one final sender address, the svnmailer
generates an additional Sender:
header with the first
item of the sender address list (which is more or less random, but
they should be all valid, right?).
If there are no sender addresses given, it uses the string
no_author
(as the original script does). That may lead to
an error while mail sending, so the best is to supply a valid
from_addr
in the [defaults]
section.
If there are no recipients, the svnmailer simply doesn't send the
mail (this will be useful in the future, when there are more notifier
types like news etc). If this is not, what you want, you have to
supply functioning to_addr
options.
All those addresses may contain substitution patterns in the form
%(name)s
. The list of values to substitute is
determined for each notification group dynamically using the
for_repos
and for_paths
regular expressions.
The substitution name author
is always defined. If not
overridden by one of the regular expressions, it contains the author
of the change (or the string no_author
if no author could be determined).
All address templates described here are empty by default. For
compatibility reasons the reply_to_addr
option can also
be written as reply_to
.
# Example
# =======
[defaults]
for_repos = .*/public$
from_addr = %(author)s@example.org
[projects]
for_paths = projects/(?P<PROJECT>[^/]+)/
to_addr = commits@%(PROJECT)s.example.org
reply_to_addr = dev@%(PROJECT)s.example.org
[home repositories]
for_paths = home/(?P<OWNER>[^/]+)/
to_addr = %(OWNER)s@example.org
[everything else]
for_paths =
fallback = yes
to_addr = svnadmin@example.org
generate_diffs
The generate_diffs
option defines which
actions diffs are generated for. It takes a space or tab separated list
of one or more of the following tokens: add
, modify
,
copy
, delete
, propchange
and
none
.
If the add
token is given and a new file is added to
the repository, the svnmailer generates a diff between an empty file and
the newly added one. If the modify
token is given and the
content of an already existing file is changed, a diff between the old
revision and the new revision of that file is generated. The
copy
token only worries about files, that are copied
and modified during one commit. The delete
token
generates a diff between the previous revision of the file and an
empty file, if a file was deleted.
If the propchange
token is given, the svnmailer also
takes care of changes in versioned properties. Whether it should
actually generate diffs for the property change action depends on the
other tokens of the generate_diffs
list. The same rules as
for files apply, except that the svnmailer never generates property
diffs for deleted files. For example:
# Example
# =======
[defaults]
generate_diffs = add copy modify propchange
Now svnmailer generates diffs, if:
- A file is added, modified or copied (and modified)
- A property is added or modified (properties cannot be copied)
If a file or property is deleted, it's written as action information
into the mail, but no content diff is generated. The default value for
generate_diffs
contains all possible actions. Mistyped
tokens are ignored. If the token list is empty, svnmailer falls back
to the default. If you really don't want diffs to be generated,
use:
[some group]
generate_diffs = none
viewcvs_base_url
If the viewcvs_base_url
option is defined and
not empty, the svnmailer generates URLs for the viewcvs repository browser.
One URL for the whole revision is placed on top and for every generated
file diff a conrete URL is written before the actual diff output. The
default value is empty.
# Example
# =======
[defaults]
viewcvs_base_url = http://svn.example.org/browse
And here is an output of a change in a sample repository (revision 5):
Author: nd
Date: Thu Jan 6 00:10:04 2005
New Revision: 5
URL: http://svn.example.org/browse?view=rev&rev=5
Log:
copied a file
Added:
foo/eggs (contents, props changed)
- copied, changed from r4, foo/spam
Copied: foo/eggs (from r4, foo/spam)
URL: http://svn.example.org/browse/foo/eggs?view=diff&rev=5&p1=foo/spam&r1=4&p2=foo/eggs&r2=5
==============================================================================
--- foo/spam (original)
+++ foo/eggs Thu Jan 6 00:10:04 2005
@@ -1,1 +1,1 @@
-This is spam.
+These are eggs.
Propchange: foo/eggs
------------------------------------------------------------------------------
color = white
Substitutions
Substitutions are a powerful feature that can simplify some
configurations very much. They base on named python format
strings. The format strings are similar to the ones you may
already know from the printf()
function of C or perl,
except that the particular formats are not determined by order, but
by name. To give a particular format a name, you just
write %(name)s
instead of %s
(for a
string). The name can be any sequence of characters.
As usual, when dealing with such format strings, if you want to
express a literal %
, you need to duplicate it
(%%
). Note that you should limit your formats to
strings (i.e. the s
format), because that's what the
svnmailer always supplies. Here is a real life example:
[defaults]
from_addr = %(author)s@example.org
Well, where are the format names and values taken from? Depending on
the option they are either fixed or determined dynamically. The former
-- simpler -- variant is used by the diff_command
option. For
this option the svnmailer defines, which format names and
values are used (label_from
, label_to
,
from
and to
). For the actual meaning of these
format names have a look at the
diff_command
description.
The dynamic definition of format names and values is a bit more
complex. This is used by the address
templates. There the list of format names (and values) is taken
from previously matched regular expressions (default for_repos
, default
for_paths
, group for_repos
and group
for_paths
-- in that order, later definitions override
earlier ones). This relies on another python feature: named matching
groups. These are like normal storing parentheses, but you can give them
a name. Instead of (to-match)
, you write
(?P<name>to-match)
in your regular
expression. Such a name has to look like a valid python
identifier. However, after a matching regular expression is
executed, the svnmailer stores such named groups for later use
separatly for each notification group. Currently these values are
supplied by the mentioned address templates. A typical use case of
this feature is:
[some group]
for_paths = projects/(?P<PROJECT>[^/]+)/
to_addr = %(PROJECT)s-commits@example.org
Configuring the Repository
Now, after you've created a config file, you can hook the svnmailer into the repository. Subversion uses so called hook scripts to perform customized actions at certain stages of a change event.
Configuring For Commit Messages
In order to run the svnmailer for normal commits you need to call
it at the post-commit stage. If your post-commit hook isn't customized
already, change into the repository/hooks
directory and copy the post-commit.tmpl
template to
post-commit
It's generally a good idea, to keep the template
file for later reference.
# change into the hooks directory
# (/var/svn/public is the repository in question)
$ cd /var/svn/public/hooks
$ # create the actual hook script from template
cp post-commit.tmpl post-commit
# edit the file
$ vi post-commit
# make it executable
$ chmod 755 post-commit
After you've copied the template open the newly created hook script with your favorite editor. You will see a lot of comments describing the purpose of the hook and giving some hints about file system permissions etc. After you've read these comments, there follows a small shell script. Typcially there are sample scripts activated, you may not want to run. If so, comment them out or delete the entries. Finally to activate the svnmailer, add the following:
# the location of svn-mailer may be customized,
# use the real location.
/usr/bin/svn-mailer --commit --config /path/to/your/config \
--repository "${REPOS}" --revision "${REV}" &
Of course, the REPOS
and REV
variables are
only available if you left the definitions below the comments. Anyway,
that's it. After you saved the file and closed your editor, you only have
to make it executable and subversion will execute it after every
commit. It is, however, a good idea to test the hook script as the user
who runs it, before doing the next commit (That is only possible, if there
are already revision stored in the repository):
# in this example wwwrun is the user the httpd runs as
$ sudo -u wwwrun ./post-commit /var/svn/public 1
Now you should receive a notification. If not, you should get a descriptive error message, what went wrong. By the way: a typical error is a non-readable configuration file.
Configuring For Revision Property Changes
Configuring for a revprop change message is mostly equal to commit
messages. If you do not allow to modify revision properties, you don't
need to care and can stop here. If you want to allow for revision
property changes, you should read the related subversion documentation regarding the
pre-revprop-change
and post-revprop-change
hooks first.
You can hook the svnmailer into post-revprop-change
to get a notification containing the new property value.
# change into the hooks directory
# (/var/svn/public is the repository in question)
$ cd /var/svn/public/hooks
$ # create the actual hook script from template
cp post-revprop-change.tmpl post-revprop-change
# edit the file
$ vi post-revprop-change
# make it executable
$ chmod 755 post-revprop-change
After opening the hook script the same warnings as for post-commits apply: remove all stuff, you don't want there. After that add the following to the script:
# the location of svn-mailer may be customized,
# use the real location.
/usr/bin/svn-mailer --propchange --config /path/to/your/config \
--repository "${REPOS}" --revision "${REV}" \
--author "${USER}" --propname "${PROPNAME}" &
The REPOS
, REV
, USER
and
PROPNAME
should be still defined, of course. Close the file,
make it executable and test it:
# in this example wwwrun is the user the httpd runs as
$ sudo -u wwwrun ./post-revprop-change /var/svn/public 1 nd svn:log
Congratulations, you're done. Happy Hacking!