##// END OF EJS Templates
hgcia: fix diffstat support
Matt Mackall -
r15776:55a85a55 default
parent child Browse files
Show More
@@ -1,279 +1,279 b''
1 # Copyright (C) 2007-8 Brendan Cully <brendan@kublai.com>
1 # Copyright (C) 2007-8 Brendan Cully <brendan@kublai.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """hooks for integrating with the CIA.vc notification service
6 """hooks for integrating with the CIA.vc notification service
7
7
8 This is meant to be run as a changegroup or incoming hook. To
8 This is meant to be run as a changegroup or incoming hook. To
9 configure it, set the following options in your hgrc::
9 configure it, set the following options in your hgrc::
10
10
11 [cia]
11 [cia]
12 # your registered CIA user name
12 # your registered CIA user name
13 user = foo
13 user = foo
14 # the name of the project in CIA
14 # the name of the project in CIA
15 project = foo
15 project = foo
16 # the module (subproject) (optional)
16 # the module (subproject) (optional)
17 #module = foo
17 #module = foo
18 # Append a diffstat to the log message (optional)
18 # Append a diffstat to the log message (optional)
19 #diffstat = False
19 #diffstat = False
20 # Template to use for log messages (optional)
20 # Template to use for log messages (optional)
21 #template = {desc}\\n{baseurl}{webroot}/rev/{node}-- {diffstat}
21 #template = {desc}\\n{baseurl}{webroot}/rev/{node}-- {diffstat}
22 # Style to use (optional)
22 # Style to use (optional)
23 #style = foo
23 #style = foo
24 # The URL of the CIA notification service (optional)
24 # The URL of the CIA notification service (optional)
25 # You can use mailto: URLs to send by email, eg
25 # You can use mailto: URLs to send by email, eg
26 # mailto:cia@cia.vc
26 # mailto:cia@cia.vc
27 # Make sure to set email.from if you do this.
27 # Make sure to set email.from if you do this.
28 #url = http://cia.vc/
28 #url = http://cia.vc/
29 # print message instead of sending it (optional)
29 # print message instead of sending it (optional)
30 #test = False
30 #test = False
31 # number of slashes to strip for url paths
31 # number of slashes to strip for url paths
32 #strip = 0
32 #strip = 0
33
33
34 [hooks]
34 [hooks]
35 # one of these:
35 # one of these:
36 changegroup.cia = python:hgcia.hook
36 changegroup.cia = python:hgcia.hook
37 #incoming.cia = python:hgcia.hook
37 #incoming.cia = python:hgcia.hook
38
38
39 [web]
39 [web]
40 # If you want hyperlinks (optional)
40 # If you want hyperlinks (optional)
41 baseurl = http://server/path/to/repo
41 baseurl = http://server/path/to/repo
42 """
42 """
43
43
44 from mercurial.i18n import _
44 from mercurial.i18n import _
45 from mercurial.node import bin, short
45 from mercurial.node import bin, short
46 from mercurial import cmdutil, patch, templater, util, mail
46 from mercurial import cmdutil, patch, templater, util, mail
47 import email.Parser
47 import email.Parser
48
48
49 import xmlrpclib
49 import xmlrpclib
50 from xml.sax import saxutils
50 from xml.sax import saxutils
51
51
52 socket_timeout = 30 # seconds
52 socket_timeout = 30 # seconds
53 try:
53 try:
54 # set a timeout for the socket so you don't have to wait so looooong
54 # set a timeout for the socket so you don't have to wait so looooong
55 # when cia.vc is having problems. requires python >= 2.3:
55 # when cia.vc is having problems. requires python >= 2.3:
56 import socket
56 import socket
57 socket.setdefaulttimeout(socket_timeout)
57 socket.setdefaulttimeout(socket_timeout)
58 except:
58 except:
59 pass
59 pass
60
60
61 HGCIA_VERSION = '0.1'
61 HGCIA_VERSION = '0.1'
62 HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia'
62 HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia'
63
63
64
64
65 class ciamsg(object):
65 class ciamsg(object):
66 """ A CIA message """
66 """ A CIA message """
67 def __init__(self, cia, ctx):
67 def __init__(self, cia, ctx):
68 self.cia = cia
68 self.cia = cia
69 self.ctx = ctx
69 self.ctx = ctx
70 self.url = self.cia.url
70 self.url = self.cia.url
71 if self.url:
71 if self.url:
72 self.url += self.cia.root
72 self.url += self.cia.root
73
73
74 def fileelem(self, path, uri, action):
74 def fileelem(self, path, uri, action):
75 if uri:
75 if uri:
76 uri = ' uri=%s' % saxutils.quoteattr(uri)
76 uri = ' uri=%s' % saxutils.quoteattr(uri)
77 return '<file%s action=%s>%s</file>' % (
77 return '<file%s action=%s>%s</file>' % (
78 uri, saxutils.quoteattr(action), saxutils.escape(path))
78 uri, saxutils.quoteattr(action), saxutils.escape(path))
79
79
80 def fileelems(self):
80 def fileelems(self):
81 n = self.ctx.node()
81 n = self.ctx.node()
82 f = self.cia.repo.status(self.ctx.p1().node(), n)
82 f = self.cia.repo.status(self.ctx.p1().node(), n)
83 url = self.url or ''
83 url = self.url or ''
84 if url and url[-1] == '/':
84 if url and url[-1] == '/':
85 url = url[:-1]
85 url = url[:-1]
86 elems = []
86 elems = []
87 for path in f[0]:
87 for path in f[0]:
88 uri = '%s/diff/%s/%s' % (url, short(n), path)
88 uri = '%s/diff/%s/%s' % (url, short(n), path)
89 elems.append(self.fileelem(path, url and uri, 'modify'))
89 elems.append(self.fileelem(path, url and uri, 'modify'))
90 for path in f[1]:
90 for path in f[1]:
91 # TODO: copy/rename ?
91 # TODO: copy/rename ?
92 uri = '%s/file/%s/%s' % (url, short(n), path)
92 uri = '%s/file/%s/%s' % (url, short(n), path)
93 elems.append(self.fileelem(path, url and uri, 'add'))
93 elems.append(self.fileelem(path, url and uri, 'add'))
94 for path in f[2]:
94 for path in f[2]:
95 elems.append(self.fileelem(path, '', 'remove'))
95 elems.append(self.fileelem(path, '', 'remove'))
96
96
97 return '\n'.join(elems)
97 return '\n'.join(elems)
98
98
99 def sourceelem(self, project, module=None, branch=None):
99 def sourceelem(self, project, module=None, branch=None):
100 msg = ['<source>', '<project>%s</project>' % saxutils.escape(project)]
100 msg = ['<source>', '<project>%s</project>' % saxutils.escape(project)]
101 if module:
101 if module:
102 msg.append('<module>%s</module>' % saxutils.escape(module))
102 msg.append('<module>%s</module>' % saxutils.escape(module))
103 if branch:
103 if branch:
104 msg.append('<branch>%s</branch>' % saxutils.escape(branch))
104 msg.append('<branch>%s</branch>' % saxutils.escape(branch))
105 msg.append('</source>')
105 msg.append('</source>')
106
106
107 return '\n'.join(msg)
107 return '\n'.join(msg)
108
108
109 def diffstat(self):
109 def diffstat(self):
110 class patchbuf(object):
110 class patchbuf(object):
111 def __init__(self):
111 def __init__(self):
112 self.lines = []
112 self.lines = []
113 # diffstat is stupid
113 # diffstat is stupid
114 self.name = 'cia'
114 self.name = 'cia'
115 def write(self, data):
115 def write(self, data):
116 self.lines.append(data)
116 self.lines += data.splitlines(True)
117 def close(self):
117 def close(self):
118 pass
118 pass
119
119
120 n = self.ctx.node()
120 n = self.ctx.node()
121 pbuf = patchbuf()
121 pbuf = patchbuf()
122 cmdutil.export(self.cia.repo, [n], fp=pbuf)
122 cmdutil.export(self.cia.repo, [n], fp=pbuf)
123 return patch.diffstat(pbuf.lines) or ''
123 return patch.diffstat(pbuf.lines) or ''
124
124
125 def logmsg(self):
125 def logmsg(self):
126 diffstat = self.cia.diffstat and self.diffstat() or ''
126 diffstat = self.cia.diffstat and self.diffstat() or ''
127 self.cia.ui.pushbuffer()
127 self.cia.ui.pushbuffer()
128 self.cia.templater.show(self.ctx, changes=self.ctx.changeset(),
128 self.cia.templater.show(self.ctx, changes=self.ctx.changeset(),
129 baseurl=self.cia.ui.config('web', 'baseurl'),
129 baseurl=self.cia.ui.config('web', 'baseurl'),
130 url=self.url, diffstat=diffstat,
130 url=self.url, diffstat=diffstat,
131 webroot=self.cia.root)
131 webroot=self.cia.root)
132 return self.cia.ui.popbuffer()
132 return self.cia.ui.popbuffer()
133
133
134 def xml(self):
134 def xml(self):
135 n = short(self.ctx.node())
135 n = short(self.ctx.node())
136 src = self.sourceelem(self.cia.project, module=self.cia.module,
136 src = self.sourceelem(self.cia.project, module=self.cia.module,
137 branch=self.ctx.branch())
137 branch=self.ctx.branch())
138 # unix timestamp
138 # unix timestamp
139 dt = self.ctx.date()
139 dt = self.ctx.date()
140 timestamp = dt[0]
140 timestamp = dt[0]
141
141
142 author = saxutils.escape(self.ctx.user())
142 author = saxutils.escape(self.ctx.user())
143 rev = '%d:%s' % (self.ctx.rev(), n)
143 rev = '%d:%s' % (self.ctx.rev(), n)
144 log = saxutils.escape(self.logmsg())
144 log = saxutils.escape(self.logmsg())
145
145
146 url = self.url
146 url = self.url
147 if url and url[-1] == '/':
147 if url and url[-1] == '/':
148 url = url[:-1]
148 url = url[:-1]
149 url = url and '<url>%s/rev/%s</url>' % (saxutils.escape(url), n) or ''
149 url = url and '<url>%s/rev/%s</url>' % (saxutils.escape(url), n) or ''
150
150
151 msg = """
151 msg = """
152 <message>
152 <message>
153 <generator>
153 <generator>
154 <name>Mercurial (hgcia)</name>
154 <name>Mercurial (hgcia)</name>
155 <version>%s</version>
155 <version>%s</version>
156 <url>%s</url>
156 <url>%s</url>
157 <user>%s</user>
157 <user>%s</user>
158 </generator>
158 </generator>
159 %s
159 %s
160 <body>
160 <body>
161 <commit>
161 <commit>
162 <author>%s</author>
162 <author>%s</author>
163 <version>%s</version>
163 <version>%s</version>
164 <log>%s</log>
164 <log>%s</log>
165 %s
165 %s
166 <files>%s</files>
166 <files>%s</files>
167 </commit>
167 </commit>
168 </body>
168 </body>
169 <timestamp>%d</timestamp>
169 <timestamp>%d</timestamp>
170 </message>
170 </message>
171 """ % \
171 """ % \
172 (HGCIA_VERSION, saxutils.escape(HGCIA_URL),
172 (HGCIA_VERSION, saxutils.escape(HGCIA_URL),
173 saxutils.escape(self.cia.user), src, author, rev, log, url,
173 saxutils.escape(self.cia.user), src, author, rev, log, url,
174 self.fileelems(), timestamp)
174 self.fileelems(), timestamp)
175
175
176 return msg
176 return msg
177
177
178
178
179 class hgcia(object):
179 class hgcia(object):
180 """ CIA notification class """
180 """ CIA notification class """
181
181
182 deftemplate = '{desc}'
182 deftemplate = '{desc}'
183 dstemplate = '{desc}\n-- \n{diffstat}'
183 dstemplate = '{desc}\n-- \n{diffstat}'
184
184
185 def __init__(self, ui, repo):
185 def __init__(self, ui, repo):
186 self.ui = ui
186 self.ui = ui
187 self.repo = repo
187 self.repo = repo
188
188
189 self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc')
189 self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc')
190 self.user = self.ui.config('cia', 'user')
190 self.user = self.ui.config('cia', 'user')
191 self.project = self.ui.config('cia', 'project')
191 self.project = self.ui.config('cia', 'project')
192 self.module = self.ui.config('cia', 'module')
192 self.module = self.ui.config('cia', 'module')
193 self.diffstat = self.ui.configbool('cia', 'diffstat')
193 self.diffstat = self.ui.configbool('cia', 'diffstat')
194 self.emailfrom = self.ui.config('email', 'from')
194 self.emailfrom = self.ui.config('email', 'from')
195 self.dryrun = self.ui.configbool('cia', 'test')
195 self.dryrun = self.ui.configbool('cia', 'test')
196 self.url = self.ui.config('web', 'baseurl')
196 self.url = self.ui.config('web', 'baseurl')
197 # Default to -1 for backward compatibility
197 # Default to -1 for backward compatibility
198 self.stripcount = int(self.ui.config('cia', 'strip', -1))
198 self.stripcount = int(self.ui.config('cia', 'strip', -1))
199 self.root = self.strip(self.repo.root)
199 self.root = self.strip(self.repo.root)
200
200
201 style = self.ui.config('cia', 'style')
201 style = self.ui.config('cia', 'style')
202 template = self.ui.config('cia', 'template')
202 template = self.ui.config('cia', 'template')
203 if not template:
203 if not template:
204 template = self.diffstat and self.dstemplate or self.deftemplate
204 template = self.diffstat and self.dstemplate or self.deftemplate
205 template = templater.parsestring(template, quoted=False)
205 template = templater.parsestring(template, quoted=False)
206 t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
206 t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
207 style, False)
207 style, False)
208 t.use_template(template)
208 t.use_template(template)
209 self.templater = t
209 self.templater = t
210
210
211 def strip(self, path):
211 def strip(self, path):
212 '''strip leading slashes from local path, turn into web-safe path.'''
212 '''strip leading slashes from local path, turn into web-safe path.'''
213
213
214 path = util.pconvert(path)
214 path = util.pconvert(path)
215 count = self.stripcount
215 count = self.stripcount
216 if count < 0:
216 if count < 0:
217 return ''
217 return ''
218 while count > 0:
218 while count > 0:
219 c = path.find('/')
219 c = path.find('/')
220 if c == -1:
220 if c == -1:
221 break
221 break
222 path = path[c + 1:]
222 path = path[c + 1:]
223 count -= 1
223 count -= 1
224 return path
224 return path
225
225
226 def sendrpc(self, msg):
226 def sendrpc(self, msg):
227 srv = xmlrpclib.Server(self.ciaurl)
227 srv = xmlrpclib.Server(self.ciaurl)
228 res = srv.hub.deliver(msg)
228 res = srv.hub.deliver(msg)
229 if res is not True and res != 'queued.':
229 if res is not True and res != 'queued.':
230 raise util.Abort(_('%s returned an error: %s') %
230 raise util.Abort(_('%s returned an error: %s') %
231 (self.ciaurl, res))
231 (self.ciaurl, res))
232
232
233 def sendemail(self, address, data):
233 def sendemail(self, address, data):
234 p = email.Parser.Parser()
234 p = email.Parser.Parser()
235 msg = p.parsestr(data)
235 msg = p.parsestr(data)
236 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
236 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
237 msg['To'] = address
237 msg['To'] = address
238 msg['From'] = self.emailfrom
238 msg['From'] = self.emailfrom
239 msg['Subject'] = 'DeliverXML'
239 msg['Subject'] = 'DeliverXML'
240 msg['Content-type'] = 'text/xml'
240 msg['Content-type'] = 'text/xml'
241 msgtext = msg.as_string()
241 msgtext = msg.as_string()
242
242
243 self.ui.status(_('hgcia: sending update to %s\n') % address)
243 self.ui.status(_('hgcia: sending update to %s\n') % address)
244 mail.sendmail(self.ui, util.email(self.emailfrom),
244 mail.sendmail(self.ui, util.email(self.emailfrom),
245 [address], msgtext)
245 [address], msgtext)
246
246
247
247
248 def hook(ui, repo, hooktype, node=None, url=None, **kwargs):
248 def hook(ui, repo, hooktype, node=None, url=None, **kwargs):
249 """ send CIA notification """
249 """ send CIA notification """
250 def sendmsg(cia, ctx):
250 def sendmsg(cia, ctx):
251 msg = ciamsg(cia, ctx).xml()
251 msg = ciamsg(cia, ctx).xml()
252 if cia.dryrun:
252 if cia.dryrun:
253 ui.write(msg)
253 ui.write(msg)
254 elif cia.ciaurl.startswith('mailto:'):
254 elif cia.ciaurl.startswith('mailto:'):
255 if not cia.emailfrom:
255 if not cia.emailfrom:
256 raise util.Abort(_('email.from must be defined when '
256 raise util.Abort(_('email.from must be defined when '
257 'sending by email'))
257 'sending by email'))
258 cia.sendemail(cia.ciaurl[7:], msg)
258 cia.sendemail(cia.ciaurl[7:], msg)
259 else:
259 else:
260 cia.sendrpc(msg)
260 cia.sendrpc(msg)
261
261
262 n = bin(node)
262 n = bin(node)
263 cia = hgcia(ui, repo)
263 cia = hgcia(ui, repo)
264 if not cia.user:
264 if not cia.user:
265 ui.debug('cia: no user specified')
265 ui.debug('cia: no user specified')
266 return
266 return
267 if not cia.project:
267 if not cia.project:
268 ui.debug('cia: no project specified')
268 ui.debug('cia: no project specified')
269 return
269 return
270 if hooktype == 'changegroup':
270 if hooktype == 'changegroup':
271 start = repo.changelog.rev(n)
271 start = repo.changelog.rev(n)
272 end = len(repo.changelog)
272 end = len(repo.changelog)
273 for rev in xrange(start, end):
273 for rev in xrange(start, end):
274 n = repo.changelog.node(rev)
274 n = repo.changelog.node(rev)
275 ctx = repo.changectx(n)
275 ctx = repo.changectx(n)
276 sendmsg(cia, ctx)
276 sendmsg(cia, ctx)
277 else:
277 else:
278 ctx = repo.changectx(n)
278 ctx = repo.changectx(n)
279 sendmsg(cia, ctx)
279 sendmsg(cia, ctx)
General Comments 0
You need to be logged in to leave comments. Login now