##// END OF EJS Templates
notify extension: generate right number of diffs
Vadim Gelfer -
r2224:e8f47dfb default
parent child Browse files
Show More
@@ -1,257 +1,258 b''
1 1 # notify.py - email notifications for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # hook extension to email notifications to people when changesets are
9 9 # committed to a repo they subscribe to.
10 10 #
11 11 # default mode is to print messages to stdout, for testing and
12 12 # configuring.
13 13 #
14 14 # to use, configure notify extension and enable in hgrc like this:
15 15 #
16 16 # [extensions]
17 17 # hgext.notify =
18 18 #
19 19 # [hooks]
20 20 # # one email for each incoming changeset
21 21 # incoming.notify = python:hgext.notify.hook
22 22 # # batch emails when many changesets incoming at one time
23 23 # changegroup.notify = python:hgext.notify.hook
24 24 #
25 25 # [notify]
26 26 # # config items go in here
27 27 #
28 28 # config items:
29 29 #
30 30 # REQUIRED:
31 31 # config = /path/to/file # file containing subscriptions
32 32 #
33 33 # OPTIONAL:
34 34 # test = True # print messages to stdout for testing
35 35 # strip = 3 # number of slashes to strip for url paths
36 36 # domain = example.com # domain to use if committer missing domain
37 37 # style = ... # style file to use when formatting email
38 38 # template = ... # template to use when formatting email
39 39 # incoming = ... # template to use when run as incoming hook
40 40 # changegroup = ... # template when run as changegroup hook
41 41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 42 # maxsubject = 67 # truncate subject line longer than this
43 43 # [email]
44 44 # from = user@host.com # email address to send as if none given
45 45 # [web]
46 46 # baseurl = http://hgserver/... # root of hg web site for browsing commits
47 47 #
48 48 # notify config file has same format as regular hgrc. it has two
49 49 # sections so you can express subscriptions in whatever way is handier
50 50 # for you.
51 51 #
52 52 # [usersubs]
53 53 # # key is subscriber email, value is ","-separated list of glob patterns
54 54 # user@host = pattern
55 55 #
56 56 # [reposubs]
57 57 # # key is glob pattern, value is ","-separated list of subscriber emails
58 58 # pattern = user@host
59 59 #
60 60 # glob patterns are matched against path to repo root.
61 61 #
62 62 # if you like, you can put notify config file in repo that users can
63 63 # push changes to, they can manage their own subscriptions.
64 64
65 65 from mercurial.demandload import *
66 66 from mercurial.i18n import gettext as _
67 67 from mercurial.node import *
68 68 demandload(globals(), 'email.Parser mercurial:commands,templater,util')
69 69 demandload(globals(), 'fnmatch socket time')
70 70
71 71 # template for single changeset can include email headers.
72 72 single_template = '''
73 73 Subject: changeset in {webroot}: {desc|firstline|strip}
74 74 From: {author}
75 75
76 76 changeset {node|short} in {root}
77 77 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
78 78 description:
79 79 \t{desc|tabindent|strip}
80 80 '''.lstrip()
81 81
82 82 # template for multiple changesets should not contain email headers,
83 83 # because only first set of headers will be used and result will look
84 84 # strange.
85 85 multiple_template = '''
86 86 changeset {node|short} in {root}
87 87 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
88 88 summary: {desc|firstline}
89 89 '''
90 90
91 91 deftemplates = {
92 92 'changegroup': multiple_template,
93 93 }
94 94
95 95 class notifier(object):
96 96 '''email notification class.'''
97 97
98 98 def __init__(self, ui, repo, hooktype):
99 99 self.ui = ui
100 100 self.ui.readconfig(self.ui.config('notify', 'config'))
101 101 self.repo = repo
102 102 self.stripcount = int(self.ui.config('notify', 'strip', 0))
103 103 self.root = self.strip(self.repo.root)
104 104 self.domain = self.ui.config('notify', 'domain')
105 105 self.sio = templater.stringio()
106 106 self.subs = self.subscribers()
107 107
108 108 mapfile = self.ui.config('notify', 'style')
109 109 template = (self.ui.config('notify', hooktype) or
110 110 self.ui.config('notify', 'template'))
111 111 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
112 112 self.sio)
113 113 if not mapfile and not template:
114 114 template = deftemplates.get(hooktype) or single_template
115 115 if template:
116 116 template = templater.parsestring(template, quoted=False)
117 117 self.t.use_template(template)
118 118
119 119 def strip(self, path):
120 120 '''strip leading slashes from local path, turn into web-safe path.'''
121 121
122 122 path = util.pconvert(path)
123 123 count = self.stripcount
124 124 while path and count >= 0:
125 125 c = path.find('/')
126 126 if c == -1:
127 127 break
128 128 path = path[c+1:]
129 129 count -= 1
130 130 return path
131 131
132 132 def fixmail(self, addr):
133 133 '''try to clean up email addresses.'''
134 134
135 135 addr = templater.email(addr.strip())
136 136 a = addr.find('@localhost')
137 137 if a != -1:
138 138 addr = addr[:a]
139 139 if '@' not in addr:
140 140 return addr + '@' + self.domain
141 141 return addr
142 142
143 143 def subscribers(self):
144 144 '''return list of email addresses of subscribers to this repo.'''
145 145
146 146 subs = {}
147 147 for user, pats in self.ui.configitems('usersubs'):
148 148 for pat in pats.split(','):
149 149 if fnmatch.fnmatch(self.repo.root, pat.strip()):
150 150 subs[self.fixmail(user)] = 1
151 151 for pat, users in self.ui.configitems('reposubs'):
152 152 if fnmatch.fnmatch(self.repo.root, pat):
153 153 for user in users.split(','):
154 154 subs[self.fixmail(user)] = 1
155 155 subs = subs.keys()
156 156 subs.sort()
157 157 return subs
158 158
159 159 def url(self, path=None):
160 160 return self.ui.config('web', 'baseurl') + (path or self.root)
161 161
162 162 def node(self, node):
163 163 '''format one changeset.'''
164 164
165 165 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
166 166 baseurl=self.ui.config('web', 'baseurl'),
167 167 root=self.repo.root,
168 168 webroot=self.root)
169 169
170 170 def send(self, node, count):
171 171 '''send message.'''
172 172
173 173 p = email.Parser.Parser()
174 174 self.sio.seek(0)
175 175 msg = p.parse(self.sio)
176 176
177 177 def fix_subject():
178 178 '''try to make subject line exist and be useful.'''
179 179
180 180 subject = msg['Subject']
181 181 if not subject:
182 182 if count > 1:
183 183 subject = _('%s: %d new changesets') % (self.root, count)
184 184 else:
185 185 changes = self.repo.changelog.read(node)
186 186 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
187 187 subject = '%s: %s' % (self.root, s)
188 188 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
189 189 if maxsubject and len(subject) > maxsubject:
190 190 subject = subject[:maxsubject-3] + '...'
191 191 del msg['Subject']
192 192 msg['Subject'] = subject
193 193
194 194 def fix_sender():
195 195 '''try to make message have proper sender.'''
196 196
197 197 sender = msg['From']
198 198 if not sender:
199 199 sender = self.ui.config('email', 'from') or self.ui.username()
200 200 if '@' not in sender or '@localhost' in sender:
201 201 sender = self.fixmail(sender)
202 202 del msg['From']
203 203 msg['From'] = sender
204 204
205 205 fix_subject()
206 206 fix_sender()
207 207
208 208 msg['X-Hg-Notification'] = 'changeset ' + short(node)
209 209 if not msg['Message-Id']:
210 210 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
211 211 (short(node), int(time.time()),
212 212 hash(self.repo.root), socket.getfqdn()))
213 213
214 214 msgtext = msg.as_string(0)
215 215 if self.ui.configbool('notify', 'test', True):
216 216 self.ui.write(msgtext)
217 217 if not msgtext.endswith('\n'):
218 218 self.ui.write('\n')
219 219 else:
220 220 mail = self.ui.sendmail()
221 221 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
222 222
223 223 def diff(self, node):
224 224 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
225 225 if maxdiff == 0:
226 226 return
227 227 fp = templater.stringio()
228 commands.dodiff(fp, self.ui, self.repo, node,
228 prev = self.repo.changelog.parents(node)[0]
229 commands.dodiff(fp, self.ui, self.repo, prev,
229 230 self.repo.changelog.tip())
230 231 difflines = fp.getvalue().splitlines(1)
231 232 if maxdiff > 0 and len(difflines) > maxdiff:
232 233 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
233 234 (len(difflines), maxdiff))
234 235 difflines = difflines[:maxdiff]
235 236 elif difflines:
236 237 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
237 238 self.sio.write(*difflines)
238 239
239 240 def hook(ui, repo, hooktype, node=None, **kwargs):
240 241 '''send email notifications to interested subscribers.
241 242
242 243 if used as changegroup hook, send one email for all changesets in
243 244 changegroup. else send one email per changeset.'''
244 245 n = notifier(ui, repo, hooktype)
245 246 if not n.subs: return True
246 247 node = bin(node)
247 248 if hooktype == 'changegroup':
248 249 start = repo.changelog.rev(node)
249 250 end = repo.changelog.count()
250 251 count = end - start
251 252 for rev in xrange(start, end):
252 253 n.node(repo.changelog.node(rev))
253 254 else:
254 255 count = 1
255 256 n.node(node)
256 257 n.diff(node)
257 258 n.send(node, count)
General Comments 0
You need to be logged in to leave comments. Login now