Login

Unfuddle-style post-commit emails - tied to a specific Django project.

Author:
jsandell
Posted:
September 20, 2008
Language:
Python
Version:
1.0
Tags:
email svn subversion post-commit
Score:
3 (after 3 ratings)

I needed a quick way to send e-mails to other people involved in a project when I committed code - so that designers would know that templatetag x was available (or fixed), etc - and am really fond of how unfuddle handles their subversion notifications. This isn't nearly as polished as theirs, but I figured it was a good place to start.

Hope someone else finds this as handy as I have.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/python
# post-commit.py
# symlinked in /path/to/repository/hooks/post-commit

import pysvn
import sys
import os
from datetime import datetime

# Begin Django bootstrap code

# You'll need to change this to match your own specific host /
# development path.

host_dir = '/var/www/hosts/example.com' 
site_dir = '%s/site' % host_dir
site_root = '%s/root' % site_dir

# I keep various versions of Django, and supporting Django apps,
# outside of the normal Python path. If you install via setup.py
# you might not need to loop this.

for p in ('/usr/local/codebase/framework', 
          '/usr/local/codebase/apps',
          site_root,
          '%s/project' % site_root):
  sys.path.append(p)

os.environ['DJANGO_SETTINGS_MODULE'] = 'project.settings'

# End Django bootstrap code

def getChangedFiles(path, revno):
  """
    Query svn repository for changes in a given changeset.

  """

  client = pysvn.Client()
  revision = pysvn.Revision(pysvn.opt_revision_kind.number, revno)
  previous = pysvn.Revision(pysvn.opt_revision_kind.number, int(revno)-1)
  changes = client.diff_summarize(
      url_or_path1 = path,
      revision1 = previous,
      revision2 = revision,
  )

  formatted = []

  if changes:
    for c in changes:
      # Create a list of two-tuples, containing the svn 'action'
      # (add, delete, modify, etc.) and the file that's been acted upon.
      formatted.append(('%s' % c.summarize_kind, '%s' % c.path))

  return tuple(formatted)

if __name__ == '__main__':

  # We should get something like 
  # /path/to/repository <revision number>
  # i.e., "/var/svn/sites/apps 46"
  #
  # argv[0], of course, is our script name.

  assert len(sys.argv) == 3

  # Django imports

  from django.core.mail import EmailMessage
  from django.template import Context, Template
  from django.conf import settings
  from django.contrib.sites.models import Site

  # Load the svn client
  client = pysvn.Client()
  
  # Create a "proper" svn url
  repo_url = 'file://%s' % sys.argv[1] 

  # Get our revision number.
  rev = sys.argv[2]

  # Send our url and the revision number to getChangedFiles
  changes = getChangedFiles(repo_url, rev)

  # Only act if there have been changes.
  if changes:
    # Attempt to ascertain which site this is for.
    site = Site.objects.all(pk=settings.SITE_ID)

    # Get the 'from' email setting from settings.py
    from_email = settings.DEFAULT_FROM_EMAIL

    # I'm pulling from 'MANAGERS', which by default is set from
    # 'ADMINS', but it could just as easily pull from a database
    # query.

    admin_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS]

    # Get the commit metadata - author, commit date, etc.
    log = client.log(repo_url,
                    revision_start = pysvn.Revision(pysvn.opt_revision_kind.head),
                    revision_end=pysvn.Revision(pysvn.opt_revision_kind.number, 
                    int(rev)))[0]

    # Convert the float timestamp into a UTC datetime object.
    stamp = datetime.utcfromtimestamp(log['date'])

    # Further convert it into something more human readable.
    friendly_date = stamp.strftime('%A %b %d, %Y')
    friendly_time = stamp.strftime('%I:%M %P')

    # Get the author / committer.
    author = log['author']

    # Get the commit message
    message = log['message']

    # Build our template context.
    c = Context(dict(
      site = site,
      author = author,
      date = friendly_date,
      time = friendly_time,
      divider = '-' * 67,
      revision = rev,
      changes = changes,
      message = message,
    ))

    # Create our subject and body templates. We can also use normal
    # templates for this, but that goes beyond the context of this
    # snippet.

    s = Template("Commit for {{ site }}")
    b = Template("""Revision: {{ revision }}
Author: {{ author }}
Date: {{ date }} {{ time }}
{{ divider }}
{{ message|default:"(no commit message)" }}
{% for action, path in changes %}
{{ action|capfirst }} {{ path }}{% endfor %}
""")

    # Render our templates.
    subject = s.render(c)
    body = b.render(c)

    # Set up our email message.
    mail = EmailMessage(subject, body, from_email, admin_list)

    # Send using text. Change to 'html' to use rich formatting.
    mail.content_subtype = 'plain'

    # Send our commit information!
    mail.send()

More like this

  1. Git recent commits template tag by david 6 years, 8 months ago
  2. Reset / Send account details email by Ciantic 4 years, 8 months ago
  3. Unobtrusive comment moderation by ubernostrum 8 years, 1 month ago
  4. Email or username authentication with masquerading by petrilli 5 years, 10 months ago
  5. Save disk space by hard-linking multiple Django installations by akaihola 8 years ago

Comments

Please login first before commenting.