summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Bergli Heier <snakebite@jvnv.net>2011-05-06 00:34:54 +0200
committerJon Bergli Heier <snakebite@jvnv.net>2011-05-06 00:34:54 +0200
commit35c828afd9aa75cd08ab5382f7a3437e346c61ce (patch)
treebab5e3d8627427a4721babf3ee7ccb139372a770
parentd164f881c4ab8c5a1ff0df5e3e9df665da2a83d2 (diff)
Update repos through an unix socket using repo hooks.
-rwxr-xr-xgitnoti.py143
1 files changed, 72 insertions, 71 deletions
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 <http://www.gnu.org/licenses/>.
-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()