# No user-serviceable parts below this line:
#
-version = "1.13"
+version = "2.6"
-import os, sys, commands, socket, urllib, subprocess, locale
+import os, sys, commands, socket, urllib, subprocess, locale, datetime
from pipes import quote as shellquote
try:
import simplejson as json # Faster, also makes us Python-2.5-compatible
import json
def do(command):
- return unicode(commands.getstatusoutput(command)[1], locale.getpreferredencoding() or 'UTF-8')
+ return unicode(commands.getstatusoutput(command)[1], locale.getlocale()[1] or 'UTF-8').encode(locale.getlocale()[1] or 'UTF-8')
class Commit:
def __init__(self, extractor, commit):
self.files = None
self.logmsg = None
self.url = None
+ self.author_date = None
+ self.commit_date = None
self.__dict__.update(extractor.__dict__)
def __unicode__(self):
"Produce a notification string from this commit."
self.url = webview
except IOError:
self.url = ""
- return self.template % self.__dict__
+ res = self.template % self.__dict__
+ return unicode(res, 'UTF-8') if not isinstance(res, unicode) else res
class GenericExtractor:
"Generic class for encapsulating data from a VCS."
booleans = ["tcp"]
numerics = ["maxchannels"]
+ strings = ["email"]
def __init__(self, arguments):
self.arguments = arguments
self.project = None
self.repo = None
# These aren't really repo data but they belong here anyway...
+ self.email = None
self.tcp = True
self.tinyifier = default_tinyifier
self.server = None
setattr(self, key, False)
elif key in GenericExtractor.numerics:
setattr(self, key, int(val))
+ elif key in GenericExtractor.strings:
+ setattr(self, key, val)
if not self.project:
sys.stderr.write("irkerhook.py: no project name set!\n")
raise SystemExit(1)
def has(dirname, paths):
"Test for existence of a list of paths."
- return all([os.path.exists(os.path.join(dirname, x)) for x in paths])
+ # all() is a python2.5 construct
+ for exists in [os.path.exists(os.path.join(dirname, x)) for x in paths]:
+ if not exists:
+ return False
+ return True
# VCS-dependent code begins here
self.repo = do("git config --get irker.repo")
self.server = do("git config --get irker.server")
self.channels = do("git config --get irker.channels")
+ self.email = do("git config --get irker.email")
self.tcp = do("git config --bool --get irker.tcp")
self.template = '%(bold)s%(project)s:%(reset)s %(green)s%(author)s%(reset)s %(repo)s:%(yellow)s%(branch)s%(reset)s * %(bold)s%(rev)s%(reset)s / %(bold)s%(files)s%(reset)s: %(logmsg)s %(brown)s%(url)s%(reset)s'
self.tinyifier = do("git config --get irker.tinyifier") or default_tinyifier
# Extract the meta-information for the commit
commit.files = do("git diff-tree -r --name-only " + shellquote(commit.commit))
commit.files = " ".join(commit.files.strip().split("\n")[1:])
- # Design choice: for git we ship only the first line, which is
+ # Design choice: for git we ship only the first message line, which is
# conventionally supposed to be a summary of the commit. Under
# other VCSes a different choice may be appropriate.
- metainfo = do("git log -1 '--pretty=format:%an <%ae>|%s' " + shellquote(commit.commit))
- (commit.author, commit.logmsg) = metainfo.split("|")
- commit.mail = commit.author.split()[-1].strip("<>")
+ commit.author_name, commit.mail, commit.logmsg = \
+ do("git log -1 '--pretty=format:%an%n%ae%n%s' " + shellquote(commit.commit)).split("\n")
# This discards the part of the author's address after @.
# Might be be nice to ship the full email address, if not
# for spammers' address harvesters - getting this wrong
# would make the freenode #commits channel into harvester heaven.
commit.author = commit.mail.split("@")[0]
+ commit.author_date, commit.commit_date = \
+ do("git log -1 '--pretty=format:%ai|%ci' " + shellquote(commit.commit)).split("|")
return commit
class SvnExtractor(GenericExtractor):
commit.branch = ""
commit.rev = "r%s" % self.id
commit.author = self.svnlook("author")
+ commit.commit_date = self.svnlook("date").partition('(')[0]
commit.files = self.svnlook("dirs-changed").strip().replace("\n", " ")
- commit.logmsg = self.svnlook("log").strip() + "\n"
+ commit.logmsg = self.svnlook("log").strip()
return commit
def svnlook(self, info):
return do("svnlook %s %s --revision %s" % (shellquote(info), shellquote(self.repository), shellquote(self.id)))
self.repo = ui.config('irker', 'repo')
self.server = ui.config('irker', 'server')
self.channels = ui.config('irker', 'channels')
+ self.email = ui.config('irker', 'email')
self.tcp = str(ui.configbool('irker', 'tcp')) # converted to bool again in do_overrides
self.template = '%(bold)s%(project)s:%(reset)s %(green)s%(author)s%(reset)s %(repo)s:%(yellow)s%(branch)s%(reset)s * %(bold)s%(rev)s%(reset)s / %(bold)s%(files)s%(reset)s: %(logmsg)s %(brown)s%(url)s%(reset)s'
self.tinyifier = ui.config('irker', 'tinyifier') or default_tinyifier
commit.rev = '%d:%s' % (ctx.rev(), commit.commit)
commit.branch = ctx.branch()
commit.author = person(ctx.user())
+ commit.author_date = \
+ datetime.datetime.fromtimestamp(ctx.date()[0]).strftime('%Y-%m-%d %H:%M:%S')
commit.logmsg = ctx.description()
# Extract changed files from status against first parent
st = self.repository.status(ctx.p1().node(), ctx.node())
return commit
def hg_hook(ui, repo, **kwds):
- # To be called from a Mercurial "commit" or "incoming" hook. Example
- # configuration:
+ # To be called from a Mercurial "commit", "incoming" or "changegroup" hook.
+ # Example configuration:
# [hooks]
# incoming.irker = python:/path/to/irkerhook.py:hg_hook
extractor = HgExtractor([(ui, repo)])
- ship(extractor, kwds['node'], False)
+ start = repo[kwds['node']].rev()
+ end = len(repo)
+ if start != end:
+ # changegroup with multiple commits, so we generate a notification
+ # for each one
+ for rev in range(start, end):
+ ship(extractor, rev, False)
+ else:
+ ship(extractor, kwds['node'], False)
# The files we use to identify a Subversion repo might occur as content
# in a git or hg repo, but the special subdirectories for those are more
print message
elif channels:
try:
- if extractor.tcp:
+ if extractor.email:
+ # We can't really figure out what our SF username is without
+ # exploring our environment. The mail pipeline doesn't care
+ # about who sent the mail, other than being from sourceforge.
+ # A better way might be to simply call mail(1)
+ sender = "irker@users.sourceforge.net"
+ msg = """From: %(sender)s
+Subject: irker json
+
+%(message)s""" % {"sender":sender, "message":message}
+ import smtplib
+ smtp = smtplib.SMTP()
+ smtp.connect()
+ smtp.sendmail(sender, extractor.email, msg)
+ smtp.quit()
+ elif extractor.tcp:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((extractor.server or default_server, IRKER_PORT))