#!/usr/bin/env python import git, os, sys from optparse import OptionParser from twisted.words.protocols import irc from twisted.internet import reactor, protocol from twisted.internet.task import LoopingCall parser = OptionParser() parser.add_option('-s', '--host') parser.add_option('-n', '--nick', default = 'git') parser.add_option('-d', '--dir') parser.add_option('-c', '--channel') parser.add_option('-i', '--interval', type = 'int', default = 30) (options, args) = parser.parse_args() if not options.host or not options.dir or not options.channel or not options.nick: parser.print_help() sys.exit(1) root = options.dir repos = None def repo_commit_msg(repo, commit): stat = '%d files,' % commit.stats.total['files'] stat += ' \00305--%d\017' % commit.stats.total['deletions'] stat += ' \00303++%d\017' % commit.stats.total['insertions'] msg = '\002%s\002 pushed to \002%s\002 by \002%s\002 (%s) %s' % ( os.path.splitext(os.path.basename(repo.path))[0], commit.id_abbrev, commit.committer.name if commit.author.name == commit.committer.name else '%s/%s' % (commit.committer.name, commit.author.name), stat, commit.summary ) return msg def check_repos(bot): global repos repo_paths = [x for x in os.listdir(root) if x.endswith('.git')] if not repos: repos = [[git.Repo('%s/%s' % (root, x)), None] for x in repo_paths] bot.gitmsg('Repos initialized: %s' % (', '.join([os.path.splitext(os.path.basename(x[0].path))[0] for x in repos]))) else: old_paths = [os.path.basename(x[0].path) for x in repos] new_paths = [x for x in repo_paths if not x in old_paths] del_paths = [x for x in old_paths if not x in repo_paths] del_repos = [x for x in repos if os.path.basename(x[0].path) in del_paths] if del_repos: bot.gitmsg('Removed repo%s: %s' % ('' if len(del_paths) == 1 else '', ', '.join([os.path.splitext(x)[0] for x in del_paths]))) for i in del_repos: repos.remove(i) new_added = [] for i in new_paths: try: r = git.Repo('%s/%s' % (root, i)) repos.append([r, None]) new_added.append(i) except: pass if new_added: bot.gitmsg('New repo%s: %s' % ('' if len(new_added) == 1 else 's', ', '.join([os.path.splitext(x)[0] for x in new_added]))) for i, v in enumerate(repos): repo = v[0] if not repo.heads: # No commits continue last = v[1] if last == None: last = repo.heads[0].commit.id repos[i][1] = last nlast = repo.heads[0].commit if last and nlast.id != last: msg = repo_commit_msg(repo, nlast) bot.gitmsg(msg) repos[i][1] = nlast.id class Bot(irc.IRCClient): nickname = options.nick def gitmsg(self, msg): self.say(options.channel, msg) def signedOn(self): self.join(options.channel) self.repeater = LoopingCall(check_repos, self) self.repeater.start(options.interval) def privmsg(self, user, channel, message): private = channel == self.nickname nick = user.split('!')[0] target = nick if private else channel messagelist = message.split() if len(messagelist) < 2: return if messagelist[0].startswith(self.nickname): cmd = messagelist[1].lower() if cmd == 'list': s = 'Repos: %s' % ', '.join([os.path.splitext(os.path.basename(x[0].path))[0] for x in repos]) self.msg(target, s) elif cmd == 'last': repo = messagelist[2].lower() if len(messagelist) > 2 else None if not repo: self.msg(target, 'Which repo?') return repo = [r for r in repos if os.path.basename(r[0].path).startswith(repo)] if len(repo) == 1: r = repo[0][0] msg = repo_commit_msg(r, r.heads[0].commit) self.msg(target, msg) elif len(repo) == 0: self.msg(target, 'No repo found.') else: self.msg(target, 'Ambiguous name: %s' % (', '.join([os.path.basename(x[0].path) for x in repos]))) class BotFactory(protocol.ReconnectingClientFactory): protocol = Bot if __name__ == '__main__': f = BotFactory() reactor.connectTCP(options.host, 6667, f) reactor.run()