X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=irkerhook.py;h=d17248a59552e8cb098b439f49ebfc9a1693d3ac;hb=14c51234d80471d44c5bed388a236f64b57e8bbf;hp=6a46622bae3dba1fce1052e7ee8988653da04270;hpb=05ab74e78a2b832489fef5488afcc5ee914396f4;p=irker.git diff --git a/irkerhook.py b/irkerhook.py index 6a46622..d17248a 100755 --- a/irkerhook.py +++ b/irkerhook.py @@ -37,9 +37,9 @@ default_channels = "irc://chat.freenode.net/#commits" # 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 @@ -47,7 +47,7 @@ except ImportError: 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): @@ -60,6 +60,8 @@ class 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." @@ -86,17 +88,20 @@ class 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 @@ -181,6 +186,8 @@ class GenericExtractor: 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) @@ -193,7 +200,11 @@ class GenericExtractor: 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 @@ -211,6 +222,7 @@ class GitExtractor(GenericExtractor): 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 @@ -260,17 +272,18 @@ class GitExtractor(GenericExtractor): # 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): @@ -299,8 +312,9 @@ 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))) @@ -341,6 +355,7 @@ class HgExtractor(GenericExtractor): 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 @@ -369,6 +384,8 @@ class HgExtractor(GenericExtractor): 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()) @@ -376,12 +393,20 @@ class HgExtractor(GenericExtractor): 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 @@ -440,7 +465,22 @@ def ship(extractor, commit, debug): 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))