From 35c828afd9aa75cd08ab5382f7a3437e346c61ce Mon Sep 17 00:00:00 2001 From: Jon Bergli Heier Date: Fri, 6 May 2011 00:34:54 +0200 Subject: Update repos through an unix socket using repo hooks. --- gitnoti.py | 143 +++++++++++++++++++++++++++++++------------------------------ 1 file changed, 72 insertions(+), 71 deletions(-) (limited to 'gitnoti.py') diff --git a/gitnoti.py b/gitnoti.py index 3bce874..a8071c4 100755 --- a/gitnoti.py +++ b/gitnoti.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 # gitnotipy - An IRC bot which announces changes to a collection of git repositories # Copyright (C) 2009-2010 Jon Bergli Heier @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import git, os, sys, time, pyinotify, netrc +import git, os, sys, time, pyinotify, netrc, SocketServer from optparse import OptionParser from twisted.words.protocols import irc from twisted.internet import reactor, protocol @@ -37,10 +37,11 @@ parser.add_option('-d', '--dir') parser.add_option('-c', '--channel') parser.add_option('-i', '--interval', type = 'int', default = 10) parser.add_option('-u', '--url') +parser.add_option('-l', '--socket') (options, args) = parser.parse_args() -if not options.host or not options.dir or not options.channel or not options.nick: +if not options.host or not options.dir or not options.channel or not options.nick or not options.socket: parser.print_help() sys.exit(1) @@ -61,10 +62,49 @@ class NotifyRepo(object): self.path = path self.bot = bot self.repo = git.Repo(path) - flags = pyinotify.EventsCodes.ALL_FLAGS - self.wdd = bot.wm.add_watch([os.path.join(path, 'refs/heads'), os.path.join(path, 'refs/tags')], flags['IN_MODIFY'] | flags['IN_CREATE']) self.heads = dict([(h.name, h.commit.hexsha) for h in self.repo.heads]) - self.tags = [t.name for t in self.repo.tags] + self.tags = set([t.name for t in self.repo.tags]) + + def updated(self): + reponame = os.path.splitext(os.path.basename(self.path))[0] + + oldheads = set(self.heads.keys()) + newheads = set([h.name for h in self.repo.heads]) + removed_heads = oldheads.difference(newheads) + for h in removed_heads: + self.bot.gitmsg('Branch \002%s\002 from repo \002%s\002 was deleted.' % + (h, reponame)) + + if not self.repo.heads: # No branches + return + for h in self.repo.heads: + last = self.heads[h.name] if h.name in self.heads else None + if h.commit.hexsha != last: + if last: # Check against last commit + commits = list(self.repo.iter_commits('%s..%s' % (last, h.name))) + elif len(h.commit.parents): # Check against parent commit + commits = list(self.repo.iter_commits('%s..%s' % (h.commit.parents[0], h.name))) + else: # Initial commit or orphan branch was pushed + commits = list(self.repo.iter_commits(h.name)) + + if not len(commits): # No commits + continue + + msg = repo_commit_msg(self.repo, h.name, commits) + self.bot.gitmsg(msg) + self.heads[h.name] = h.commit.hexsha + + newtags = set([t.name for t in self.repo.tags]) + removed_tags = self.tags.difference(newtags) + for t in removed_tags: + self.tags.remove(t) + self.bot.gitmsg('Tag \002%s\002 from repo \002%s\002 was deleted.' % + (t, reponame)) + + for t in (t for t in self.repo.tags if t.name not in self.tags): + self.bot.gitmsg('New tag \002%s\002 for repo \002%s\002 points to \002%s\002' % (t.name, reponame, t.commit.hexsha[:7])) + self.tags.add(t.name) + def repo_commit_msg(repo, branch, commits): reponame = os.path.splitext(os.path.basename(repo.working_dir))[0] @@ -129,83 +169,25 @@ class ReposNotifyEvent(pyinotify.ProcessEvent): repos[event.pathname] = NotifyRepo(self.bot, event.pathname) self.bot.gitmsg('New repo: %s' % os.path.splitext(event.name)[0]) - def updated_repo(self, event): - pathname = event.pathname - while len(pathname) > 1 and not pathname in repos: - pathname = os.path.dirname(pathname) - if len(pathname) == 1 or not pathname in repos: - return - - # Check for lock file - if event.pathname.endswith('.lock'): - newname = event.pathname[:-5] - i = 0 - # Wait max 10 seconds until file is moved - while not os.access(newname, os.F_OK) and os.access(event.pathname, os.F_OK) and i < 10: - time.sleep(1) - i += 1 - - # Assume deleted if neither file exists. - if not os.access(newname, os.F_OK) and not os.access(event.pathname, os.F_OK): - if os.path.dirname(newname).endswith('refs/heads'): - del repos[pathname].heads[os.path.basename(newname)] - self.bot.gitmsg('Branch \002%s\002 from repo \002%s\002 was deleted.' % - (os.path.basename(newname), os.path.splitext(os.path.basename(pathname))[0])) - elif os.path.dirname(newname).endswith('refs/tags'): - repos[pathname].tags.remove(os.path.basename(newname)) - self.bot.gitmsg('Tag \002%s\002 from repo \002%s\002 was deleted.' % - (os.path.basename(newname), os.path.splitext(os.path.basename(pathname))[0])) - return - - l = repos[pathname] - repo = l.repo - if not repo.heads: # No branches - return - for h in repo.heads: - last = l.heads[h.name] if h.name in l.heads else None - if h.commit.hexsha != last: - if last: # Check against last commit - commits = list(repo.iter_commits('%s..%s' % (last, h.name))) - elif len(h.commit.parents): # Check against parent commit - commits = list(repo.iter_commits('%s..%s' % (h.commit.parents[0], h.name))) - else: # Initial commit or orphan branch was pushed - commits = list(repo.iter_commits(h.name)) - - if not len(commits): # No commits - continue - - msg = repo_commit_msg(repo, h.name, commits) - self.bot.gitmsg(msg) - l.heads[h.name] = h.commit.hexsha - - for t in (t for t in repo.tags if t.name not in l.tags): - reponame = os.path.splitext(os.path.basename(repo.working_dir))[0] - self.bot.gitmsg('New tag \002%s\002 for repo \002%s\002 points to \002%s\002' % (t.name, reponame, t.commit.hexsha[:7])) - l.tags.append(t.name) - def process_IN_CREATE(self, event): - if os.path.dirname(event.pathname) == root: - # Ignore files in root directory - if os.path.isdir(event.pathname): - self.new_repo(event) - else: - self.updated_repo(event) + # Ignore directories outside of, and files in the root directory + if os.path.dirname(event.pathname) == root and os.path.isdir(event.pathname): + self.new_repo(event) def process_IN_DELETE(self, event): if event.pathname in repos: - self.bot.wm.rm_watch(repos[event.pathname].wdd.values()) del repos[event.pathname] self.bot.gitmsg('Removed repo: %s' % os.path.splitext(event.name)[0]) - def process_IN_MODIFY(self, event): - self.updated_repo(event) - def check_notifies(bot): bot.notifier.process_events() while bot.notifier.check_events(): bot.notifier.read_events() bot.notifier.process_events() +# holds the current active Bot instance +bot = None + class Bot(irc.IRCClient): nickname = options.nick @@ -218,7 +200,7 @@ class Bot(irc.IRCClient): repos[fullpath] = NotifyRepo(self, fullpath) def gitmsg(self, msg): - self.say(options.channel, msg.encode('utf-8')) + self.msg(options.channel, msg.encode('utf-8')) def signedOn(self): self.join(options.channel) @@ -234,6 +216,14 @@ class Bot(irc.IRCClient): self.repeater = LoopingCall(check_notifies, self) self.repeater.start(options.interval) + global bot + bot = self + + def connectionLost(self, reason): + global bot + bot = None + irc.IRCClient.connectionLost(self, reason) + def get_repo(self, name, target): repo = [v for k, v in repos.iteritems() if os.path.basename(k).startswith(name)] if len(repo) == 1: @@ -358,7 +348,18 @@ class Bot(irc.IRCClient): class BotFactory(protocol.ReconnectingClientFactory): protocol = Bot +class UnixDatagramServer(protocol.DatagramProtocol): + def datagramReceived(self, datagram, addr): + name = datagram.strip() + if not name in repos: + return + repo = repos[name] + repo.updated() + if __name__ == '__main__': f = BotFactory() reactor.connectTCP(options.host, options.port, f) + if os.access(options.socket, os.F_OK): + os.unlink(options.socket) + reactor.listenUNIXDatagram(options.socket, UnixDatagramServer()) reactor.run() -- cgit v1.2.3