##// END OF EJS Templates
Make diffstat optional for patchbomb script.
Thomas Arendsen Hein -
r1136:d4518885 default
parent child Browse files
Show More
@@ -1,252 +1,253 b''
1 1 #!/usr/bin/python
2 2 #
3 3 # Interactive script for sending a collection of Mercurial changesets
4 4 # as a series of patch emails.
5 5 #
6 6 # The series is started off with a "[PATCH 0 of N]" introduction,
7 7 # which describes the series as a whole.
8 8 #
9 9 # Each patch email has a Subject line of "[PATCH M of N] ...", using
10 10 # the first line of the changeset description as the subject text.
11 11 # The message contains two or three body parts:
12 12 #
13 13 # The remainder of the changeset description.
14 14 #
15 15 # [Optional] If the diffstat program is installed, the result of
16 16 # running diffstat on the patch.
17 17 #
18 18 # The patch itself, as generated by "hg export".
19 19 #
20 20 # Each message refers to all of its predecessors using the In-Reply-To
21 21 # and References headers, so they will show up as a sequence in
22 22 # threaded mail and news readers, and in mail archives.
23 23 #
24 24 # For each changeset, you will be prompted with a diffstat summary and
25 25 # the changeset summary, so you can be sure you are sending the right
26 26 # changes.
27 27 #
28 28 # It is best to run this script with the "-n" (test only) flag before
29 29 # firing it up "for real", in which case it will use your pager to
30 30 # display each of the messages that it would send.
31 31 #
32 32 # To configure a default mail host, add a section like this to your
33 33 # hgrc file:
34 34 #
35 35 # [smtp]
36 36 # host = my_mail_host
37 37 # port = 1025
38 38 #
39 39 # To configure other defaults, add a section like this to your hgrc
40 40 # file:
41 41 #
42 42 # [patchbomb]
43 43 # from = My Name <my@email>
44 44 # to = recipient1, recipient2, ...
45 45 # cc = cc1, cc2, ...
46 46
47 47 from email.MIMEMultipart import MIMEMultipart
48 48 from email.MIMEText import MIMEText
49 49 from mercurial import commands
50 50 from mercurial import fancyopts
51 51 from mercurial import hg
52 52 from mercurial import ui
53 53 import os
54 54 import popen2
55 55 import readline
56 56 import smtplib
57 57 import socket
58 58 import sys
59 59 import tempfile
60 60 import time
61 61
62 62 def diffstat(patch):
63 63 fd, name = tempfile.mkstemp()
64 64 try:
65 65 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
66 66 try:
67 67 for line in patch: print >> p.tochild, line
68 68 p.tochild.close()
69 69 if p.wait(): return
70 70 fp = os.fdopen(fd, 'r')
71 71 stat = []
72 72 for line in fp: stat.append(line.lstrip())
73 73 last = stat.pop()
74 74 stat.insert(0, last)
75 75 stat = ''.join(stat)
76 76 if stat.startswith('0 files'): raise ValueError
77 77 return stat
78 78 except: raise
79 79 finally:
80 80 try: os.unlink(name)
81 81 except: pass
82 82
83 83 def patchbomb(ui, repo, *revs, **opts):
84 84 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
85 85 if default: prompt += ' [%s]' % default
86 86 prompt += rest
87 87 while True:
88 88 r = raw_input(prompt)
89 89 if r: return r
90 90 if default is not None: return default
91 91 if empty_ok: return r
92 92 ui.warn('Please enter a valid value.\n')
93 93
94 94 def confirm(s):
95 95 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
96 96 raise ValueError
97 97
98 98 def cdiffstat(summary, patch):
99 99 s = diffstat(patch)
100 100 if s:
101 101 if summary:
102 102 ui.write(summary, '\n')
103 103 ui.write(s, '\n')
104 104 confirm('Does the diffstat above look okay')
105 105 return s
106 106
107 107 def makepatch(patch, idx, total):
108 108 desc = []
109 109 node = None
110 110 body = ''
111 111 for line in patch:
112 112 if line.startswith('#'):
113 113 if line.startswith('# Node ID'): node = line.split()[-1]
114 114 continue
115 115 if line.startswith('diff -r'): break
116 116 desc.append(line)
117 117 if not node: raise ValueError
118 118
119 119 #body = ('\n'.join(desc[1:]).strip() or
120 120 # 'Patch subject is complete summary.')
121 121 #body += '\n\n\n'
122 122
123 123 if opts['diffstat']:
124 124 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
125 125 body += '\n'.join(patch)
126 126 msg = MIMEText(body)
127 127 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
128 128 if subj.endswith('.'): subj = subj[:-1]
129 129 msg['Subject'] = subj
130 130 msg['X-Mercurial-Node'] = node
131 131 return msg
132 132
133 133 start_time = int(time.time())
134 134
135 135 def genmsgid(id):
136 136 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
137 137
138 138 patches = []
139 139
140 140 class exportee:
141 141 def __init__(self, container):
142 142 self.lines = []
143 143 self.container = container
144 144 self.name = 'email'
145 145
146 146 def write(self, data):
147 147 self.lines.append(data)
148 148
149 149 def close(self):
150 150 self.container.append(''.join(self.lines).split('\n'))
151 151 self.lines = []
152 152
153 153 commands.export(ui, repo, *args, **{'output': exportee(patches),
154 154 'text': None})
155 155
156 156 jumbo = []
157 157 msgs = []
158 158
159 159 ui.write('This patch series consists of %d patches.\n\n' % len(patches))
160 160
161 161 for p, i in zip(patches, range(len(patches))):
162 162 jumbo.extend(p)
163 163 msgs.append(makepatch(p, i + 1, len(patches)))
164 164
165 165 ui.write('\nWrite the introductory message for the patch series.\n\n')
166 166
167 167 sender = (opts['from'] or ui.config('patchbomb', 'from') or
168 168 prompt('From', ui.username()))
169 169
170 170 msg = MIMEMultipart()
171 171 msg['Subject'] = '[PATCH 0 of %d] %s' % (
172 172 len(patches),
173 173 opts['subject'] or
174 174 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
175 175 to = opts['to'] or ui.config('patchbomb', 'to') or prompt('To')
176 176 to = [t.strip() for t in to.split(',')]
177 177 cc = (opts['cc'] or ui.config('patchbomb', 'cc') or
178 178 prompt('Cc', default = ''))
179 179 cc = (cc and [c.strip() for c in cc.split(',')]) or []
180 180
181 181 ui.write('Finish with ^D or a dot on a line by itself.\n\n')
182 182
183 183 body = []
184 184
185 185 while True:
186 186 try: l = raw_input()
187 187 except EOFError: break
188 188 if l == '.': break
189 189 body.append(l)
190 190
191 191 msg.attach(MIMEText('\n'.join(body) + '\n'))
192 192
193 193 ui.write('\n')
194 194
195 d = cdiffstat('Final summary:\n', jumbo)
196 if d: msg.attach(MIMEText(d))
195 if opts['diffstat']:
196 d = cdiffstat('Final summary:\n', jumbo)
197 if d: msg.attach(MIMEText(d))
197 198
198 199 msgs.insert(0, msg)
199 200
200 201 if not opts['test']:
201 202 s = smtplib.SMTP()
202 203 s.connect(host = ui.config('smtp', 'host', 'mail'),
203 204 port = int(ui.config('smtp', 'port', 25)))
204 205
205 206 parent = None
206 207 tz = time.strftime('%z')
207 208 for m in msgs:
208 209 try:
209 210 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
210 211 except TypeError:
211 212 m['Message-Id'] = genmsgid('patchbomb')
212 213 if parent:
213 214 m['In-Reply-To'] = parent
214 215 else:
215 216 parent = m['Message-Id']
216 217 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz
217 218 start_time += 1
218 219 m['From'] = sender
219 220 m['To'] = ', '.join(to)
220 221 if cc: m['Cc'] = ', '.join(cc)
221 222 ui.status('Sending ', m['Subject'], ' ...\n')
222 223 if opts['test']:
223 224 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
224 225 fp.write(m.as_string(0))
225 226 fp.write('\n')
226 227 fp.close()
227 228 else:
228 229 s.sendmail(sender, to + cc, m.as_string(0))
229 230 if not opts['test']:
230 231 s.close()
231 232
232 233 if __name__ == '__main__':
233 234 optspec = [('c', 'cc', [], 'email addresses of copy recipients'),
234 235 ('d', 'diffstat', None, 'add diffstat output to messages'),
235 236 ('f', 'from', '', 'email address of sender'),
236 237 ('n', 'test', None, 'print messages that would be sent'),
237 238 ('s', 'subject', '', 'subject of introductory message'),
238 239 ('t', 'to', [], 'email addresses of recipients')]
239 240 options = {}
240 241 try:
241 242 args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts + optspec,
242 243 options)
243 244 except fancyopts.getopt.GetoptError, inst:
244 245 u = ui.ui()
245 246 u.warn('error: %s' % inst)
246 247 sys.exit(1)
247 248
248 249 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
249 250 not options["noninteractive"])
250 251 repo = hg.repository(ui = u)
251 252
252 253 patchbomb(u, repo, *args, **options)
General Comments 0
You need to be logged in to leave comments. Login now