##// END OF EJS Templates
notify: fix indentation in module docstring
Martin Geisler -
r9105:6188f2cc default
parent child Browse files
Show More
@@ -1,289 +1,289 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 of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 '''hooks for sending email notifications at commit/push time
9 9
10 10 Subscriptions can be managed through a hgrc file. Default mode is to print
11 11 messages to stdout, for testing and configuring.
12 12
13 13 To use, configure the notify extension and enable it in hgrc like this:
14 14
15 [extensions]
16 hgext.notify =
15 [extensions]
16 hgext.notify =
17 17
18 [hooks]
19 # one email for each incoming changeset
20 incoming.notify = python:hgext.notify.hook
21 # batch emails when many changesets incoming at one time
22 changegroup.notify = python:hgext.notify.hook
18 [hooks]
19 # one email for each incoming changeset
20 incoming.notify = python:hgext.notify.hook
21 # batch emails when many changesets incoming at one time
22 changegroup.notify = python:hgext.notify.hook
23 23
24 [notify]
25 # config items go here
24 [notify]
25 # config items go here
26 26
27 Required configuration items:
27 Required configuration items:
28 28
29 config = /path/to/file # file containing subscriptions
29 config = /path/to/file # file containing subscriptions
30 30
31 Optional configuration items:
31 Optional configuration items:
32 32
33 test = True # print messages to stdout for testing
34 strip = 3 # number of slashes to strip for url paths
35 domain = example.com # domain to use if committer missing domain
36 style = ... # style file to use when formatting email
37 template = ... # template to use when formatting email
38 incoming = ... # template to use when run as incoming hook
39 changegroup = ... # template when run as changegroup hook
40 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 maxsubject = 67 # truncate subject line longer than this
42 diffstat = True # add a diffstat before the diff content
43 sources = serve # notify if source of incoming changes in this list
44 # (serve == ssh or http, push, pull, bundle)
45 [email]
46 from = user@host.com # email address to send as if none given
47 [web]
48 baseurl = http://hgserver/... # root of hg web site for browsing commits
33 test = True # print messages to stdout for testing
34 strip = 3 # number of slashes to strip for url paths
35 domain = example.com # domain to use if committer missing domain
36 style = ... # style file to use when formatting email
37 template = ... # template to use when formatting email
38 incoming = ... # template to use when run as incoming hook
39 changegroup = ... # template when run as changegroup hook
40 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 maxsubject = 67 # truncate subject line longer than this
42 diffstat = True # add a diffstat before the diff content
43 sources = serve # notify if source of incoming changes in this list
44 # (serve == ssh or http, push, pull, bundle)
45 [email]
46 from = user@host.com # email address to send as if none given
47 [web]
48 baseurl = http://hgserver/... # root of hg web site for browsing commits
49 49
50 The notify config file has same format as a regular hgrc file. It has two
51 sections so you can express subscriptions in whatever way is handier for you.
50 The notify config file has same format as a regular hgrc file. It has two
51 sections so you can express subscriptions in whatever way is handier for you.
52 52
53 [usersubs]
54 # key is subscriber email, value is ","-separated list of glob patterns
55 user@host = pattern
53 [usersubs]
54 # key is subscriber email, value is ","-separated list of glob patterns
55 user@host = pattern
56 56
57 [reposubs]
58 # key is glob pattern, value is ","-separated list of subscriber emails
59 pattern = user@host
57 [reposubs]
58 # key is glob pattern, value is ","-separated list of subscriber emails
59 pattern = user@host
60 60
61 Glob patterns are matched against path to repository root.
61 Glob patterns are matched against path to repository root.
62 62
63 If you like, you can put notify config file in repository that users can push
64 changes to, they can manage their own subscriptions.
63 If you like, you can put notify config file in repository that users can push
64 changes to, they can manage their own subscriptions.
65 65 '''
66 66
67 67 from mercurial.i18n import _
68 68 from mercurial import patch, cmdutil, templater, util, mail
69 69 import email.Parser, 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 cfg = self.ui.config('notify', 'config')
101 101 if cfg:
102 102 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
103 103 self.repo = repo
104 104 self.stripcount = int(self.ui.config('notify', 'strip', 0))
105 105 self.root = self.strip(self.repo.root)
106 106 self.domain = self.ui.config('notify', 'domain')
107 107 self.test = self.ui.configbool('notify', 'test', True)
108 108 self.charsets = mail._charsets(self.ui)
109 109 self.subs = self.subscribers()
110 110
111 111 mapfile = self.ui.config('notify', 'style')
112 112 template = (self.ui.config('notify', hooktype) or
113 113 self.ui.config('notify', 'template'))
114 114 self.t = cmdutil.changeset_templater(self.ui, self.repo,
115 115 False, None, mapfile, False)
116 116 if not mapfile and not template:
117 117 template = deftemplates.get(hooktype) or single_template
118 118 if template:
119 119 template = templater.parsestring(template, quoted=False)
120 120 self.t.use_template(template)
121 121
122 122 def strip(self, path):
123 123 '''strip leading slashes from local path, turn into web-safe path.'''
124 124
125 125 path = util.pconvert(path)
126 126 count = self.stripcount
127 127 while count > 0:
128 128 c = path.find('/')
129 129 if c == -1:
130 130 break
131 131 path = path[c+1:]
132 132 count -= 1
133 133 return path
134 134
135 135 def fixmail(self, addr):
136 136 '''try to clean up email addresses.'''
137 137
138 138 addr = util.email(addr.strip())
139 139 if self.domain:
140 140 a = addr.find('@localhost')
141 141 if a != -1:
142 142 addr = addr[:a]
143 143 if '@' not in addr:
144 144 return addr + '@' + self.domain
145 145 return addr
146 146
147 147 def subscribers(self):
148 148 '''return list of email addresses of subscribers to this repo.'''
149 149 subs = set()
150 150 for user, pats in self.ui.configitems('usersubs'):
151 151 for pat in pats.split(','):
152 152 if fnmatch.fnmatch(self.repo.root, pat.strip()):
153 153 subs.add(self.fixmail(user))
154 154 for pat, users in self.ui.configitems('reposubs'):
155 155 if fnmatch.fnmatch(self.repo.root, pat):
156 156 for user in users.split(','):
157 157 subs.add(self.fixmail(user))
158 158 return [mail.addressencode(self.ui, s, self.charsets, self.test)
159 159 for s in sorted(subs)]
160 160
161 161 def url(self, path=None):
162 162 return self.ui.config('web', 'baseurl') + (path or self.root)
163 163
164 164 def node(self, ctx):
165 165 '''format one changeset.'''
166 166 self.t.show(ctx, changes=ctx.changeset(),
167 167 baseurl=self.ui.config('web', 'baseurl'),
168 168 root=self.repo.root, webroot=self.root)
169 169
170 170 def skipsource(self, source):
171 171 '''true if incoming changes from this source should be skipped.'''
172 172 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
173 173 return source not in ok_sources
174 174
175 175 def send(self, ctx, count, data):
176 176 '''send message.'''
177 177
178 178 p = email.Parser.Parser()
179 179 msg = p.parsestr(data)
180 180
181 181 # store sender and subject
182 182 sender, subject = msg['From'], msg['Subject']
183 183 del msg['From'], msg['Subject']
184 184 # store remaining headers
185 185 headers = msg.items()
186 186 # create fresh mime message from msg body
187 187 text = msg.get_payload()
188 188 # for notification prefer readability over data precision
189 189 msg = mail.mimeencode(self.ui, text, self.charsets, self.test)
190 190 # reinstate custom headers
191 191 for k, v in headers:
192 192 msg[k] = v
193 193
194 194 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
195 195
196 196 # try to make subject line exist and be useful
197 197 if not subject:
198 198 if count > 1:
199 199 subject = _('%s: %d new changesets') % (self.root, count)
200 200 else:
201 201 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
202 202 subject = '%s: %s' % (self.root, s)
203 203 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
204 204 if maxsubject and len(subject) > maxsubject:
205 205 subject = subject[:maxsubject-3] + '...'
206 206 msg['Subject'] = mail.headencode(self.ui, subject,
207 207 self.charsets, self.test)
208 208
209 209 # try to make message have proper sender
210 210 if not sender:
211 211 sender = self.ui.config('email', 'from') or self.ui.username()
212 212 if '@' not in sender or '@localhost' in sender:
213 213 sender = self.fixmail(sender)
214 214 msg['From'] = mail.addressencode(self.ui, sender,
215 215 self.charsets, self.test)
216 216
217 217 msg['X-Hg-Notification'] = 'changeset %s' % ctx
218 218 if not msg['Message-Id']:
219 219 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
220 220 (ctx, int(time.time()),
221 221 hash(self.repo.root), socket.getfqdn()))
222 222 msg['To'] = ', '.join(self.subs)
223 223
224 224 msgtext = msg.as_string(0)
225 225 if self.test:
226 226 self.ui.write(msgtext)
227 227 if not msgtext.endswith('\n'):
228 228 self.ui.write('\n')
229 229 else:
230 230 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
231 231 (len(self.subs), count))
232 232 mail.sendmail(self.ui, util.email(msg['From']),
233 233 self.subs, msgtext)
234 234
235 235 def diff(self, ctx, ref=None):
236 236
237 237 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
238 238 prev = ctx.parents()[0].node()
239 239 ref = ref and ref.node() or ctx.node()
240 240 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
241 241 difflines = ''.join(chunks).splitlines()
242 242
243 243 if self.ui.configbool('notify', 'diffstat', True):
244 244 s = patch.diffstat(difflines)
245 245 # s may be nil, don't include the header if it is
246 246 if s:
247 247 self.ui.write('\ndiffstat:\n\n%s' % s)
248 248
249 249 if maxdiff == 0:
250 250 return
251 251 elif maxdiff > 0 and len(difflines) > maxdiff:
252 252 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
253 253 self.ui.write(msg % (len(difflines), maxdiff))
254 254 difflines = difflines[:maxdiff]
255 255 elif difflines:
256 256 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
257 257
258 258 self.ui.write("\n".join(difflines))
259 259
260 260 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
261 261 '''send email notifications to interested subscribers.
262 262
263 263 if used as changegroup hook, send one email for all changesets in
264 264 changegroup. else send one email per changeset.'''
265 265
266 266 n = notifier(ui, repo, hooktype)
267 267 ctx = repo[node]
268 268
269 269 if not n.subs:
270 270 ui.debug(_('notify: no subscribers to repository %s\n') % n.root)
271 271 return
272 272 if n.skipsource(source):
273 273 ui.debug(_('notify: changes have source "%s" - skipping\n') % source)
274 274 return
275 275
276 276 ui.pushbuffer()
277 277 if hooktype == 'changegroup':
278 278 start, end = ctx.rev(), len(repo)
279 279 count = end - start
280 280 for rev in xrange(start, end):
281 281 n.node(repo[rev])
282 282 n.diff(ctx, repo['tip'])
283 283 else:
284 284 count = 1
285 285 n.node(ctx)
286 286 n.diff(ctx)
287 287
288 288 data = ui.popbuffer()
289 289 n.send(ctx, count, data)
@@ -1,218 +1,218 b''
1 1 notify extension - hooks for sending email notifications at commit/push time
2 2
3 3 Subscriptions can be managed through a hgrc file. Default mode is to print
4 4 messages to stdout, for testing and configuring.
5 5
6 6 To use, configure the notify extension and enable it in hgrc like this:
7 7
8 [extensions]
9 hgext.notify =
8 [extensions]
9 hgext.notify =
10 10
11 [hooks]
12 # one email for each incoming changeset
13 incoming.notify = python:hgext.notify.hook
14 # batch emails when many changesets incoming at one time
15 changegroup.notify = python:hgext.notify.hook
11 [hooks]
12 # one email for each incoming changeset
13 incoming.notify = python:hgext.notify.hook
14 # batch emails when many changesets incoming at one time
15 changegroup.notify = python:hgext.notify.hook
16 16
17 [notify]
18 # config items go here
17 [notify]
18 # config items go here
19 19
20 Required configuration items:
20 Required configuration items:
21 21
22 config = /path/to/file # file containing subscriptions
22 config = /path/to/file # file containing subscriptions
23 23
24 Optional configuration items:
24 Optional configuration items:
25 25
26 test = True # print messages to stdout for testing
27 strip = 3 # number of slashes to strip for url paths
28 domain = example.com # domain to use if committer missing domain
29 style = ... # style file to use when formatting email
30 template = ... # template to use when formatting email
31 incoming = ... # template to use when run as incoming hook
32 changegroup = ... # template when run as changegroup hook
33 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
34 maxsubject = 67 # truncate subject line longer than this
35 diffstat = True # add a diffstat before the diff content
36 sources = serve # notify if source of incoming changes in this list
37 # (serve == ssh or http, push, pull, bundle)
38 [email]
39 from = user@host.com # email address to send as if none given
40 [web]
41 baseurl = http://hgserver/... # root of hg web site for browsing commits
26 test = True # print messages to stdout for testing
27 strip = 3 # number of slashes to strip for url paths
28 domain = example.com # domain to use if committer missing domain
29 style = ... # style file to use when formatting email
30 template = ... # template to use when formatting email
31 incoming = ... # template to use when run as incoming hook
32 changegroup = ... # template when run as changegroup hook
33 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
34 maxsubject = 67 # truncate subject line longer than this
35 diffstat = True # add a diffstat before the diff content
36 sources = serve # notify if source of incoming changes in this list
37 # (serve == ssh or http, push, pull, bundle)
38 [email]
39 from = user@host.com # email address to send as if none given
40 [web]
41 baseurl = http://hgserver/... # root of hg web site for browsing commits
42 42
43 The notify config file has same format as a regular hgrc file. It has two
44 sections so you can express subscriptions in whatever way is handier for you.
43 The notify config file has same format as a regular hgrc file. It has two
44 sections so you can express subscriptions in whatever way is handier for you.
45 45
46 [usersubs]
47 # key is subscriber email, value is ","-separated list of glob patterns
48 user@host = pattern
46 [usersubs]
47 # key is subscriber email, value is ","-separated list of glob patterns
48 user@host = pattern
49 49
50 [reposubs]
51 # key is glob pattern, value is ","-separated list of subscriber emails
52 pattern = user@host
50 [reposubs]
51 # key is glob pattern, value is ","-separated list of subscriber emails
52 pattern = user@host
53 53
54 Glob patterns are matched against path to repository root.
54 Glob patterns are matched against path to repository root.
55 55
56 If you like, you can put notify config file in repository that users can push
57 changes to, they can manage their own subscriptions.
56 If you like, you can put notify config file in repository that users can push
57 changes to, they can manage their own subscriptions.
58 58
59 59 no commands defined
60 60 % commit
61 61 adding a
62 62 % clone
63 63 updating working directory
64 64 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 65 % commit
66 66 % pull (minimal config)
67 67 pulling from ../a
68 68 searching for changes
69 69 adding changesets
70 70 adding manifests
71 71 adding file changes
72 72 added 1 changesets with 1 changes to 1 files
73 73 Content-Type: text/plain; charset="us-ascii"
74 74 MIME-Version: 1.0
75 75 Content-Transfer-Encoding: 7bit
76 76 Date:
77 77 Subject: changeset in test-notify/b: b
78 78 From: test
79 79 X-Hg-Notification: changeset 0647d048b600
80 80 Message-Id:
81 81 To: baz, foo@bar
82 82
83 83 changeset 0647d048b600 in test-notify/b
84 84 details: test-notify/b?cmd=changeset;node=0647d048b600
85 85 description: b
86 86
87 87 diffs (6 lines):
88 88
89 89 diff -r cb9a9f314b8b -r 0647d048b600 a
90 90 --- a/a Thu Jan 01 00:00:00 1970 +0000
91 91 +++ b/a Thu Jan 01 00:00:01 1970 +0000
92 92 @@ -1,1 +1,2 @@
93 93 a
94 94 +a
95 95 (run 'hg update' to get a working copy)
96 96 % fail for config file is missing
97 97 rolling back last transaction
98 98 pull failed
99 99 % pull
100 100 rolling back last transaction
101 101 pulling from ../a
102 102 searching for changes
103 103 adding changesets
104 104 adding manifests
105 105 adding file changes
106 106 added 1 changesets with 1 changes to 1 files
107 107 Content-Type: text/plain; charset="us-ascii"
108 108 MIME-Version: 1.0
109 109 Content-Transfer-Encoding: 7bit
110 110 X-Test: foo
111 111 Date:
112 112 Subject: b
113 113 From: test@test.com
114 114 X-Hg-Notification: changeset 0647d048b600
115 115 Message-Id:
116 116 To: baz@test.com, foo@bar
117 117
118 118 changeset 0647d048b600
119 119 description:
120 120 b
121 121 diffs (6 lines):
122 122
123 123 diff -r cb9a9f314b8b -r 0647d048b600 a
124 124 --- a/a Thu Jan 01 00:00:00 1970 +0000
125 125 +++ b/a Thu Jan 01 00:00:01 1970 +0000
126 126 @@ -1,1 +1,2 @@
127 127 a
128 128 +a
129 129 (run 'hg update' to get a working copy)
130 130 % pull
131 131 rolling back last transaction
132 132 pulling from ../a
133 133 searching for changes
134 134 adding changesets
135 135 adding manifests
136 136 adding file changes
137 137 added 1 changesets with 1 changes to 1 files
138 138 Content-Type: text/plain; charset="us-ascii"
139 139 MIME-Version: 1.0
140 140 Content-Transfer-Encoding: 7bit
141 141 X-Test: foo
142 142 Date:
143 143 Subject: b
144 144 From: test@test.com
145 145 X-Hg-Notification: changeset 0647d048b600
146 146 Message-Id:
147 147 To: baz@test.com, foo@bar
148 148
149 149 changeset 0647d048b600
150 150 description:
151 151 b
152 152 diffstat:
153 153
154 154 a | 1 +
155 155 1 files changed, 1 insertions(+), 0 deletions(-)
156 156
157 157 diffs (6 lines):
158 158
159 159 diff -r cb9a9f314b8b -r 0647d048b600 a
160 160 --- a/a Thu Jan 01 00:00:00 1970 +0000
161 161 +++ b/a Thu Jan 01 00:00:01 1970 +0000
162 162 @@ -1,1 +1,2 @@
163 163 a
164 164 +a
165 165 (run 'hg update' to get a working copy)
166 166 % test merge
167 167 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
168 168 created new head
169 169 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
170 170 (branch merge, don't forget to commit)
171 171 pulling from ../a
172 172 searching for changes
173 173 adding changesets
174 174 adding manifests
175 175 adding file changes
176 176 added 2 changesets with 0 changes to 1 files
177 177 Content-Type: text/plain; charset="us-ascii"
178 178 MIME-Version: 1.0
179 179 Content-Transfer-Encoding: 7bit
180 180 X-Test: foo
181 181 Date:
182 182 Subject: adda2
183 183 From: test@test.com
184 184 X-Hg-Notification: changeset 0a184ce6067f
185 185 Message-Id:
186 186 To: baz@test.com, foo@bar
187 187
188 188 changeset 0a184ce6067f
189 189 description:
190 190 adda2
191 191 diffstat:
192 192
193 193 a | 1 +
194 194 1 files changed, 1 insertions(+), 0 deletions(-)
195 195
196 196 diffs (6 lines):
197 197
198 198 diff -r cb9a9f314b8b -r 0a184ce6067f a
199 199 --- a/a Thu Jan 01 00:00:00 1970 +0000
200 200 +++ b/a Thu Jan 01 00:00:02 1970 +0000
201 201 @@ -1,1 +1,2 @@
202 202 a
203 203 +a
204 204 Content-Type: text/plain; charset="us-ascii"
205 205 MIME-Version: 1.0
206 206 Content-Transfer-Encoding: 7bit
207 207 X-Test: foo
208 208 Date:
209 209 Subject: merge
210 210 From: test@test.com
211 211 X-Hg-Notification: changeset 22c88b85aa27
212 212 Message-Id:
213 213 To: baz@test.com, foo@bar
214 214
215 215 changeset 22c88b85aa27
216 216 description:
217 217 merge
218 218 (run 'hg update' to get a working copy)
General Comments 0
You need to be logged in to leave comments. Login now