Subversion post commit email hook, in Ruby
I’m a big believer in tools. Personally, I believe that better engineers tend to use better tools, but that’s a subject to explore in another post.
Here’s a handy Ruby script that sends a descriptive email after each subversion checkin. The script is based on the one written by Elliott Hughes. Somehow I seem to rewrite this script every time I take a new job, so I’m pleased to release this one into the public domain.
The script lists all files that were added, removed, or modified. Here’s a screenshot:

If you’re going to use this with a sizable engineering team, I recommend changing the HTML email to include a photo of the person who committed the change. That’ll help everyone get acquainted.
Also, it’s nice to have the “modified” lines link to the diff. You can view diffs with ViewCVS. If you find that ViewCVS is too heavyweight (or ugly), I wrote a diff viewing Ruby CGI that gets the job done. I’ll post that shortly. Update: see my subsequent blob post, Subversion diff viewer CGI, in Ruby.
Please excuse my amateurish Ruby. I’m still learning.
To install:
- Copy the script to hooks/post-commit in your subversion repository.
- Make the script executable.
- Modify the ADDRESS constant at the top of the file. Modify other constants if necessary.
- (optional) Adjust my beautiful HTML to suit your needs.
You can download the script or copy and paste it from below:
#!/usr/bin/ruby -w # svn-email.rb # # Send svn checkin email, based on a script by Elliott Hughes. To # install, copy this file into your repository's hooks/ directory as # "post-commit". Don't forget to chmod a+x post-commit # # Author: Elliott Hughes, Adam Doppelt # Version: 0.2 require 'cgi' # constants ADDRESS = "MODIFY_THIS@MODIFY_THIS.com" SENDMAIL = "/usr/sbin/sendmail" #SENDMAIL = "/usr/bin/tee" # for debugging SVNLOOK = "/usr/bin/svnlook" # convert a list of files to HTML def filesToHtml(title, list, revision = nil) return "" if list.length == 0 # truncate if too big list[200..-1] = "..." if list.length > 200 result = "" result << "\\n<h3>#{title}</h3>\\n" result << "<div class=\"files\">\\n" list.each do |file| result << " " result << CGI.escapeHTML(file) result << "<br/>\\n" end result << "</div>\\n" result end # Subversion's commit-email.pl suggests that svnlook might create files. Dir.chdir("/tmp") # process ARGV repo = ARGV.shift revision = ARGV.shift raise "bad args" if !repo || !revision # # Get the overview information. # info = `#{SVNLOOK} info #{repo} -r #{revision}`.split("\\n") author = info.shift date = info.shift size = info.shift subject = info[0] comment = info.join("\\n") # # iterate changed files # added = [] modified = [] removed = [] props_modified = [] `#{SVNLOOK} changed #{repo} -r #{revision}`.split("\\n").each do |line| op = line[0,1] props = line[1,1] file = line[4..-1] # escape the filename file = CGI.escapeHTML(file) case op when 'A' then added.push(file) when 'U' then modified.push(file) when 'D' then removed.push(file) end props_modified.push(file) if props == 'U' end # # build the message body # body = <<EOF <html> <head> <style type="text/css"> .main { font : 10pt verdana; background: white; width: 95% } .main h3 { margin: 15px 0px 5px 0px; } .comment { border: 1px solid #dddddd; padding: 5px; } .files { border: 1px solid #dddddd; padding: 5px; background: #eeeeff; } </style> </head> <body> <div class="main"> <h3>Revision #{revision} by #{CGI.escapeHTML(author)}</h3> <div class="comment"> #{CGI.escapeHTML(comment).split("\\n").join("<br/>")} </div> #{filesToHtml("Added Paths", added)} #{filesToHtml("Modified Paths", modified, revision)} #{filesToHtml("Removed Paths", removed)} #{filesToHtml("Property Changed", props_modified)} </div> </body> </html> EOF # # Write the mail headers # header = "" header << "To: #{ADDRESS}\\n" header << "From: #{ADDRESS}\\n" header << "Subject: [svn] [#{revision}] #{subject}\\n" header << "MIME-Version: 1.0\\n" header << "Content-Type: text/html; charset=UTF-8\\n" header << "Content-Transfer-Encoding: 8bit\\n" header << "\\n" # # Send the mail. # begin fd = open("|#{SENDMAIL} #{ADDRESS}", "w") fd.print(header) fd.print(body) rescue exit 1 end fd.close
Friday, October 6th 2006 at 9:30 am
Nice … the scruffy default emails are history!
Monday, November 6th 2006 at 8:26 pm
This is great. If your ever in Honolulu I’ll buy you a beer.
Tuesday, March 27th 2007 at 11:25 pm
This is great. I’m having a problem executing the script though (I’m a ruby/unix newbie trying to get this checkin emails going for our svn repository).
To narrow the problem down, I’m running the script directly – ruby post-commit
where both both are valid.
The script seems to run fine but I’m not seing any emails sent to my email address that I changed the script to.
Any ideas?