c6fafb9f163a12c3cee1a1e4afaa7c4b8eb9fb49
[debian/godebian-client.git] / ciabot.py
1 #!/usr/bin/env python
2 # Copyright (c) 2010 Eric S. Raymond <esr@thyrsus.com>
3 # Copyright (c) 2010 Bernd Zeimetz <bzed@debian.org>
4 # Distributed under BSD terms.
5 #
6 # This script contains porcelain and porcelain byproducts.
7 # It's Python because the Python standard libraries avoid portability/security
8 # issues raised by callouts in the ancestral Perl and sh scripts. It should
9 # be compatible back to Python 2.1.5
10 #
11 # usage: ciabot.py [-V] [-n] [-p projectname] [refname [commits...]]
12 #
13 # This script is meant to be run either in a post-commit hook or in an
14 # update hook. If there's nothing unusual about your hosting setup,
15 # you can specify the project name and repo with config variables and
16 # avoid having to modify this script. Try it with -n to see the
17 # notification mail dumped to stdout and verify that it looks
18 # sane. With -V it dumps its version and exits.
19 #
20 # In post-commit, run it without arguments. It will query for
21 # current HEAD and the latest commit ID to get the information it
22 # needs.
23 #
24 # In update, call it with a refname followed by a list of commits:
25 # You want to reverse the order git rev-list emits becxause it lists
26 # from most recent to oldest.
27 #
28 # /path/to/ciabot.py ${refname} $(git rev-list ${oldhead}..${newhead} | tac)
29 #
30 # Configuration variables affecting this script:
31 # hooks.cia-project = name of the project (required)
32 # hooks.repo = name of the project repo for gitweb/cgit purposes
33 # hooks.cia-xmlrpc = if true, ship notifications via XML-RPC
34 # hooks.revformat = format in which the revision is shown
35 #
36 # The ciabot.repo defaults to ciabot.project lowercased.
37 #
38 # The revformat variable may have the following values
39 # raw -> full hex ID of commit
40 # short -> first 12 chars of hex ID
41 # describe = -> desescription relative to last tag, falling back to short
42 # The default is 'describe'.
43 #
44 # Note: the shell ancestors of this script used mail, not XML-RPC, in
45 # order to avoid stalling until timeout when the CIA XML-RPC server is
46 # down. It is unknown whether this is still an issue in 2010, but we
47 # default to mail just in case. (Using XML-RPC guarantees that multiple
48 # notifications shipped from a commit hook will arrive in order.)
49 #
50
51 import os, sys, commands, socket, urllib, getpass
52
53 # Changeset URL prefix for your repo: when the commit ID is appended
54 # to this, it should point at a CGI that will display the commit
55 # through gitweb or something similar. The defaults will probably
56 # work if you have a typical gitweb/cgit setup.
57 #
58 urlprefix = "http://%(host)s/?p=%(repo)s;a=commit;h="
59
60 # The template used to generate the XML messages to CIA. You can make
61 # visible changes to the IRC-bot notification lines by hacking this.
62 # The default will produce a notfication line that looks like this:
63 #
64 # ${project}: ${author} ${repo}:${branch} * ${rev} ${files}: ${logmsg} ${url}
65 #
66 # By omitting $files you can collapse the files part to a single slash.
67 xml = '''\
68 <message>
69 <generator>
70 <name>CIA Python client for Git</name>
71 <version>%(version)s</version>
72 <url>%(generator)s</url>
73 </generator>
74 <source>
75 <project>%(project)s</project>
76 <branch>%(repo)s:%(branch)s</branch>
77 </source>
78 <timestamp>%(ts)s</timestamp>
79 <body>
80 <commit>
81 <author>%(author)s</author>
82 <revision>%(rev)s</revision>
83 <files>
84 %(files)s
85 </files>
86 <log>%(logmsg)s %(url)s</log>
87 <url>%(url)s</url>
88 </commit>
89 </body>
90 </message>
91 '''
92
93 #
94 # No user-serviceable parts below this line:
95 #
96
97 # Where to ship e-mail notifications.
98 toaddr = "cia@cia.navi.cx"
99
100 # Identify the generator script.
101 # Should only change when the script itself gets a new home and maintainer.
102 generator = "http://deb.li/ciabot"
103 version = "3.3-bz1"
104
105 try:
106 from anyjson import serialize, deserialize
107 except ImportError:
108 #there is no anyjson/cjson on alioth yet.
109 from json import write as serialize, read as deserialize
110
111
112 def do(command):
113 return commands.getstatusoutput(command)[1]
114
115 def report(refname, merged):
116 "Generate a commit notification to be reported to CIA"
117
118 # Try to tinyfy a reference to a web view for this commit.
119 try:
120 postdata = serialize({"method": "add_url", 'params': ["%s%s" %(urlprefix, merged) ], 'id':'ciabot.py'})
121 respdata = urllib.urlopen("http://deb.li/rpc/json", postdata).read()
122 resp = deserialize(respdata)
123 if resp['error'] != None:
124 raise Exception(resp['error'])
125 url = "http://deb.li/%s" %(resp['result'], )
126
127 except:
128 url = urlprefix + merged
129
130 branch = os.path.basename(refname)
131
132 # Compute a description for the revision
133 if revformat == 'raw':
134 rev = merged
135 elif revformat == 'short':
136 rev = ''
137 else: # rev == 'describe'
138 rev = do("git describe %s 2>/dev/null" % merged)
139 if not rev:
140 rev = merged[:12]
141
142 # Extract the neta-information for the commit
143 files=do("git diff-tree -r --name-only '"+ merged +"' | sed -e '1d' -e 's-.*-<file>&</file>-'")
144 metainfo = do("git log -1 '--pretty=format:%an <%ae>%n%at%n%s' " + merged)
145 (author, ts, logmsg) = metainfo.split("\n")
146
147 # This discards the part of the authors addrsss after @.
148 # Might be be nice to ship the full email address, if not
149 # for spammers' address harvesters - getting this wrong
150 # would make the freenode #commits channel into harvester heaven.
151 if getpass.getuser().startswith('git'):
152 author = author.replace("<", "").split("@")[0].split()[-1]
153 else:
154 author = getpass.getuser()
155
156 # This ignores the timezone. Not clear what to do with it...
157 ts = ts.strip().split()[0]
158
159 context = locals()
160 context.update(globals())
161
162 out = xml % context
163
164 message = '''\
165 Message-ID: <%(merged)s.%(author)s@%(project)s>
166 From: %(fromaddr)s
167 To: %(toaddr)s
168 Content-type: text/xml
169 Subject: DeliverXML
170
171 %(out)s''' % locals()
172
173 return message
174
175 if __name__ == "__main__":
176 import getopt
177
178 # Get all config variables
179 revformat = do("git config --get hooks.revformat")
180 project = do("git config --get hooks.cia-project")
181 repo = do("git config --get hooks.repo")
182 xmlrpc = do("git config --get hooks.cia-xmlrpc")
183 xmlrpc = xmlrpc and xmlrpc != "false"
184
185 host = do("git config --get hooks.hostname")
186 if not host:
187 host = socket.getfqdn()
188 fromaddr = "%s@%s" %(getpass.getuser(), host)
189
190 try:
191 (options, arguments) = getopt.getopt(sys.argv[1:], "np:V")
192 except getopt.GetoptError, msg:
193 print "ciabot.py: " + str(msg)
194 raise SystemExit, 1
195
196 notify = True
197 for (switch, val) in options:
198 if switch == '-p':
199 project = val
200 elif switch == '-n':
201 notify = False
202 elif switch == '-V':
203 print "ciabot.py: version", version
204 sys.exit(0)
205
206 # Cough and die if user has not specified a project
207 if not project:
208 sys.stderr.write("ciabot.py: no project specified, bailing out.\n")
209 sys.exit(1)
210
211 if not repo:
212 repo = project.lower()
213
214 urlprefix = urlprefix % globals()
215
216 # The script wants a reference to head followed by the list of
217 # commit ID to report about.
218 if len(arguments) == 0:
219 refname = do("git symbolic-ref HEAD 2>/dev/null")
220 merges = [do("git rev-parse HEAD")]
221 else:
222 refname = arguments[0]
223 merges = arguments[1:]
224
225 if notify:
226 if xmlrpc:
227 import xmlrpclib
228 server = xmlrpclib.Server('http://cia.navi.cx/RPC2');
229 else:
230 import smtplib
231 server = smtplib.SMTP('localhost')
232
233 for merged in merges:
234 message = report(refname, merged)
235 if not notify:
236 print message
237 elif xmlrpc:
238 server.hub.deliver(message)
239 else:
240 server.sendmail(fromaddr, [toaddr], message)
241
242 if notify:
243 if not xmlrpc:
244 server.quit()
245
246 #End