-
Notifications
You must be signed in to change notification settings - Fork 4
/
gitbot.py
executable file
·114 lines (99 loc) · 4 KB
/
gitbot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/python3
"""
Gitbot should be called from the repositories root path, which should be named
$reponame.git (default in gitolite). It takes the current cwd to determine the
repositories name. It takes the same arguments as the post-update hook of git
(surprise). So you can just symlink hooks/post-update to this file and set it
to be executable.
"""
from hub import HubBot
from sleekxmpp.exceptions import IqError
from sleekxmpp.xmlstream import ET
import logging, warnings
import os, select, sys, subprocess, socket
class GitBot(HubBot):
LOCALPART = "gitolite"
PASSWORD = ""
PUBSUB = "git@"+HubBot.FEED
BASE_PATH = "/var/lib/gitolite/repositories"
xmlns = "http://hub.sotecware.net/xmpp/git-post-update"
def __init__(self, repo, repoPath, refs):
super(GitBot, self).__init__(self.LOCALPART, None, self.PASSWORD)
self.repo = repo
self.repoPath = repoPath
self.refs = refs
def _submit(self, repoName, refPath):
tree = ET.Element("{{{0}}}git".format(self.xmlns))
repo = ET.SubElement(tree, "{{{0}}}repository".format(self.xmlns))
repo.text = repoName
ref = ET.SubElement(tree, "{{{0}}}ref".format(self.xmlns))
ref.text = refPath
try:
with open(refPath, "r") as f:
sha = f.read()
except FileNotFoundError:
pass
else:
newRef = ET.SubElement(tree, "{{{0}}}new-ref".format(self.xmlns))
newRef.set("sha", sha)
self._refToETree(newRef, refPath)
self.pubsub.publish(self.FEED, self.PUBSUB,
payload=tree,
block=True)
def _personRefToETree(self, parent, nodeName, line):
node = ET.SubElement(parent, "{{{0}}}{1}".format(self.xmlns, nodeName))
components = line.split(" ")
email = components[-3]
name = " ".join(components[1:-3])
node.text = name
node.set("email", email)
def _refToETree(self, parent, ref):
output = subprocess.check_output(["git", "cat-file", "-p", ref]).decode().split("\n")
for i, line in enumerate(output):
line = line.strip()
if line.startswith("parent "):
commit = ET.SubElement(parent, "{{{0}}}parent".format(self.xmlns))
elif line.startswith("author "):
self._personRefToETree(parent, "author", line)
elif line.startswith("committer "):
self._personRefToETree(parent, "committer", line)
elif not line:
break
message = ET.SubElement(parent, "{{{0}}}headline".format(self.xmlns))
try:
message.text = output[i+1]
except IndexError:
pass
def sessionStart(self, event):
super(GitBot, self).sessionStart(event)
# create the pubsub feed if neccessary
iq = self.pubsub.get_nodes(self.FEED)
items = iq['disco_items']['items']
for server, node, _ in items:
if server == self.FEED and node == self.PUBSUB:
break
else:
self.pubsub.create_node(self.FEED, self.PUBSUB)
# publish events for each ref which got updated
try:
for ref in self.refs:
self._submit(self.repo, ref)
print("please be patient, but just kill me if I take longer than \
five seconds")
finally:
# see SleekXMPP issue #193
self.auto_reconnect = False
self.disconnect(reconnect=False, wait=False)
if __name__ == "__main__":
logging.basicConfig(level=logging.ERROR,
format='%(levelname)-8s %(message)s')
repo_path = os.getcwd().rstrip("/")
if repo_path.startswith(GitBot.BASE_PATH):
repo_name = repo_path[len(GitBot.BASE_PATH)+1:]
if repo_name.endswith(".git"):
repo_name = repo_name[:-len(".git")]
else:
repo_name = os.path.splitext(os.path.basename(repo_path.rstrip("/")))
refs = sys.argv[1:]
bot = GitBot(repo_name, repo_path, refs)
bot.run()