From e5f4d0ad1d36a298e46d7c4e86851f5fc658ee59 Mon Sep 17 00:00:00 2001 From: Jon Bergli Heier Date: Sun, 27 Dec 2009 01:51:04 +0100 Subject: Use inotify to detect changes. This replaces the manual polling used earlier. Note that we still need to "poll" inotify to detect changes. --- gitnoti.py | 130 +++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 42 deletions(-) diff --git a/gitnoti.py b/gitnoti.py index 854d1e3..02949fe 100755 --- a/gitnoti.py +++ b/gitnoti.py @@ -1,17 +1,18 @@ #!/usr/bin/env python -import git, os, sys +import git, os, sys, time from optparse import OptionParser from twisted.words.protocols import irc from twisted.internet import reactor, protocol from twisted.internet.task import LoopingCall +import pyinotify 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) +parser.add_option('-i', '--interval', type = 'int', default = 10) (options, args) = parser.parse_args() @@ -21,6 +22,9 @@ if not options.host or not options.dir or not options.channel or not options.nic root = options.dir +if root[-1] == '/': + root = root[:-1] + repos = None def repo_commit_msg(repo, commit): @@ -36,55 +40,95 @@ def repo_commit_msg(repo, commit): ) 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] +class ReposNotifyEvent(pyinotify.ProcessEvent): + def new_repo(self, event): + flags = pyinotify.EventsCodes.ALL_FLAGS + heads = '%s/refs/heads' % event.pathname + + i = 0 + # Wait max 10 seconds for refs/heads/ to appear. + while not os.access(heads, os.F_OK) and i < 10: + time.sleep(1) + i += 1 + if not os.access(heads, os.F_OK): + self.bot.gitmsg('Repo %s was found but couldn''t locate refs/heads/ (repo NOT added).' % os.path.splitext(event.name)[0]) + return + + wdd = self.bot.wm.add_watch(heads, flags['IN_MODIFY'] | flags['IN_CREATE']) + repos[event.pathname] = [None, None, wdd] + 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 + l = repos[pathname] + if not l[0]: + l[0] = git.Repo(pathname) + repo = l[0] if not repo.heads: # No commits - continue - last = v[1] - if last == None: - last = repo.heads[0].commit.id - repos[i][1] = last + return + last = l[1] nlast = repo.heads[0].commit - if last and nlast.id != last: + if nlast.id != last: msg = repo_commit_msg(repo, nlast) - bot.gitmsg(msg) - repos[i][1] = nlast.id + self.bot.gitmsg(msg) + l[1] = nlast.id + + def process_IN_CREATE(self, event): + if os.path.dirname(event.pathname) == root: + self.new_repo(event) + else: + self.updated_repo(event) + + def process_IN_DELETE(self, event): + if event.pathname in repos: + self.bot.wm.rm_watch(repos[event.pathname][2].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() class Bot(irc.IRCClient): nickname = options.nick + def initial_add(self): + global repos + if not repos: + repos = {} + for path in (x for x in os.listdir(root) if x.endswith('.git')): + try: + r = git.Repo('%s/%s' % (root, path)) + except: + continue + flags = pyinotify.EventsCodes.ALL_FLAGS + wdd = self.wm.add_watch('%s/%s/refs/heads' % (root, path), flags['IN_MODIFY'] | flags['IN_CREATE']) + repos['%s/%s' % (root, path)] = [r, r.heads[0].commit.id, wdd] + def gitmsg(self, msg): self.say(options.channel, msg) def signedOn(self): self.join(options.channel) - self.repeater = LoopingCall(check_repos, self) + self.wm = pyinotify.WatchManager() + flags = pyinotify.EventsCodes.ALL_FLAGS + self.wm.add_watch(root, flags['IN_DELETE'] | flags['IN_CREATE'] | flags['IN_ONLYDIR']) + self.rne = ReposNotifyEvent() + self.rne.bot = self + self.notifier = pyinotify.Notifier(self.wm, self.rne, timeout = options.interval) + + self.initial_add() + + self.repeater = LoopingCall(check_notifies, self) self.repeater.start(options.interval) def privmsg(self, user, channel, message): @@ -97,16 +141,18 @@ class Bot(irc.IRCClient): 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]) + s = 'Repos: %s' % ', '.join([os.path.splitext(os.path.basename(x))[0] for x in repos.keys()]) 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)] + repo = [(k, v) for k, v in repos.iteritems() if os.path.basename(k).startswith(repo)] if len(repo) == 1: - r = repo[0][0] + path = repo[0][0] + repo = repo[0][1] + r = repo[0] or git.Repo(path) msg = repo_commit_msg(r, r.heads[0].commit) self.msg(target, msg) elif len(repo) == 0: -- cgit v1.2.3