Mercurial > hg > mercurial-crew-with-dirclash
annotate contrib/patchbomb @ 1619:1ba0d7041ac4
Distinguish removed and deleted files. Tests are not fixed yet.
hg status will now show "R filename" for "hg rm"ed files and
"! filename" for files which were deleted manually.
Manually deleted files are considered unmodified.
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Thu, 12 Jan 2006 13:58:36 +0100 |
parents | da3f1121721b |
children |
rev | line source |
---|---|
875 | 1 #!/usr/bin/python |
2 # | |
3 # Interactive script for sending a collection of Mercurial changesets | |
4 # as a series of patch emails. | |
5 # | |
6 # The series is started off with a "[PATCH 0 of N]" introduction, | |
7 # which describes the series as a whole. | |
8 # | |
9 # Each patch email has a Subject line of "[PATCH M of N] ...", using | |
10 # the first line of the changeset description as the subject text. | |
11 # The message contains two or three body parts: | |
12 # | |
13 # The remainder of the changeset description. | |
14 # | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
15 # [Optional] If the diffstat program is installed, the result of |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
16 # running diffstat on the patch. |
875 | 17 # |
18 # The patch itself, as generated by "hg export". | |
19 # | |
20 # Each message refers to all of its predecessors using the In-Reply-To | |
21 # and References headers, so they will show up as a sequence in | |
22 # threaded mail and news readers, and in mail archives. | |
23 # | |
24 # For each changeset, you will be prompted with a diffstat summary and | |
25 # the changeset summary, so you can be sure you are sending the right | |
26 # changes. | |
27 # | |
28 # It is best to run this script with the "-n" (test only) flag before | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
29 # firing it up "for real", in which case it will use your pager to |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
30 # display each of the messages that it would send. |
875 | 31 # |
32 # To configure a default mail host, add a section like this to your | |
33 # hgrc file: | |
34 # | |
35 # [smtp] | |
36 # host = my_mail_host | |
37 # port = 1025 | |
1226
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
38 # tls = yes # or omit if not needed |
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
39 # username = user # if SMTP authentication required |
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
40 # password = password # if SMTP authentication required - PLAINTEXT |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
41 # |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
42 # To configure other defaults, add a section like this to your hgrc |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
43 # file: |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
44 # |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
45 # [patchbomb] |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
46 # from = My Name <my@email> |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
47 # to = recipient1, recipient2, ... |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
48 # cc = cc1, cc2, ... |
875 | 49 |
50 from email.MIMEMultipart import MIMEMultipart | |
51 from email.MIMEText import MIMEText | |
52 from mercurial import commands | |
53 from mercurial import fancyopts | |
54 from mercurial import hg | |
55 from mercurial import ui | |
56 import os | |
57 import popen2 | |
58 import smtplib | |
59 import socket | |
60 import sys | |
61 import tempfile | |
62 import time | |
63 | |
1204
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
64 try: |
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
65 # readline gives raw_input editing capabilities, but is not |
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
66 # present on windows |
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
67 import readline |
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
68 except ImportError: pass |
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
69 |
875 | 70 def diffstat(patch): |
71 fd, name = tempfile.mkstemp() | |
72 try: | |
73 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name) | |
74 try: | |
75 for line in patch: print >> p.tochild, line | |
76 p.tochild.close() | |
77 if p.wait(): return | |
78 fp = os.fdopen(fd, 'r') | |
79 stat = [] | |
80 for line in fp: stat.append(line.lstrip()) | |
81 last = stat.pop() | |
82 stat.insert(0, last) | |
83 stat = ''.join(stat) | |
84 if stat.startswith('0 files'): raise ValueError | |
85 return stat | |
86 except: raise | |
87 finally: | |
88 try: os.unlink(name) | |
89 except: pass | |
90 | |
91 def patchbomb(ui, repo, *revs, **opts): | |
92 def prompt(prompt, default = None, rest = ': ', empty_ok = False): | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
93 if default: prompt += ' [%s]' % default |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
94 prompt += rest |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
95 while True: |
875 | 96 r = raw_input(prompt) |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
97 if r: return r |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
98 if default is not None: return default |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
99 if empty_ok: return r |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
100 ui.warn('Please enter a valid value.\n') |
875 | 101 |
102 def confirm(s): | |
103 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'): | |
104 raise ValueError | |
105 | |
106 def cdiffstat(summary, patch): | |
107 s = diffstat(patch) | |
108 if s: | |
109 if summary: | |
110 ui.write(summary, '\n') | |
111 ui.write(s, '\n') | |
112 confirm('Does the diffstat above look okay') | |
113 return s | |
114 | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
115 def makepatch(patch, idx, total): |
875 | 116 desc = [] |
117 node = None | |
1135
e455d91f6259
Variable 'body' was missing in patchbomb script.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
1118
diff
changeset
|
118 body = '' |
875 | 119 for line in patch: |
120 if line.startswith('#'): | |
121 if line.startswith('# Node ID'): node = line.split()[-1] | |
122 continue | |
123 if line.startswith('diff -r'): break | |
124 desc.append(line) | |
125 if not node: raise ValueError | |
1118
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
126 |
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
127 #body = ('\n'.join(desc[1:]).strip() or |
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
128 # 'Patch subject is complete summary.') |
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
129 #body += '\n\n\n' |
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
130 |
1604
da3f1121721b
add --plain option to patchbomb.
Vadim Gelfer <vadim.gelfer@gmail.com>
parents:
1603
diff
changeset
|
131 if opts['plain']: |
da3f1121721b
add --plain option to patchbomb.
Vadim Gelfer <vadim.gelfer@gmail.com>
parents:
1603
diff
changeset
|
132 while patch and patch[0].startswith('# '): patch.pop(0) |
da3f1121721b
add --plain option to patchbomb.
Vadim Gelfer <vadim.gelfer@gmail.com>
parents:
1603
diff
changeset
|
133 if patch: patch.pop(0) |
da3f1121721b
add --plain option to patchbomb.
Vadim Gelfer <vadim.gelfer@gmail.com>
parents:
1603
diff
changeset
|
134 while patch and not patch[0].strip(): patch.pop(0) |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
135 if opts['diffstat']: |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
136 body += cdiffstat('\n'.join(desc), patch) + '\n\n' |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
137 body += '\n'.join(patch) |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
138 msg = MIMEText(body) |
875 | 139 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip()) |
140 if subj.endswith('.'): subj = subj[:-1] | |
141 msg['Subject'] = subj | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
142 msg['X-Mercurial-Node'] = node |
875 | 143 return msg |
144 | |
145 start_time = int(time.time()) | |
146 | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
147 def genmsgid(id): |
875 | 148 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn()) |
149 | |
150 patches = [] | |
151 | |
152 class exportee: | |
153 def __init__(self, container): | |
154 self.lines = [] | |
155 self.container = container | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
156 self.name = 'email' |
875 | 157 |
158 def write(self, data): | |
159 self.lines.append(data) | |
160 | |
161 def close(self): | |
162 self.container.append(''.join(self.lines).split('\n')) | |
163 self.lines = [] | |
164 | |
1032
706c590c9060
Get patchbomb working with tip again.
Bryan O'Sullivan <bos@serpentine.com>
parents:
998
diff
changeset
|
165 commands.export(ui, repo, *args, **{'output': exportee(patches), |
1603
5352a5407dc1
make patchbomb work with recent changes to export
Vadim Gelfer <vadim.gelfer@gmail.com>
parents:
1226
diff
changeset
|
166 'switch_parent': False, |
1032
706c590c9060
Get patchbomb working with tip again.
Bryan O'Sullivan <bos@serpentine.com>
parents:
998
diff
changeset
|
167 'text': None}) |
875 | 168 |
169 jumbo = [] | |
170 msgs = [] | |
171 | |
172 ui.write('This patch series consists of %d patches.\n\n' % len(patches)) | |
173 | |
174 for p, i in zip(patches, range(len(patches))): | |
175 jumbo.extend(p) | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
176 msgs.append(makepatch(p, i + 1, len(patches))) |
875 | 177 |
178 ui.write('\nWrite the introductory message for the patch series.\n\n') | |
179 | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
180 sender = (opts['from'] or ui.config('patchbomb', 'from') or |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
181 prompt('From', ui.username())) |
875 | 182 |
183 msg = MIMEMultipart() | |
184 msg['Subject'] = '[PATCH 0 of %d] %s' % ( | |
185 len(patches), | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
186 opts['subject'] or |
875 | 187 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches))) |
1154
c3cb9f39a91f
patchbomb: fix up confusion between strings and lists of strings.
bos@serpentine.internal.keyresearch.com
parents:
1136
diff
changeset
|
188 |
c3cb9f39a91f
patchbomb: fix up confusion between strings and lists of strings.
bos@serpentine.internal.keyresearch.com
parents:
1136
diff
changeset
|
189 def getaddrs(opt, prpt, default = None): |
c3cb9f39a91f
patchbomb: fix up confusion between strings and lists of strings.
bos@serpentine.internal.keyresearch.com
parents:
1136
diff
changeset
|
190 addrs = opts[opt] or (ui.config('patchbomb', opt) or |
c3cb9f39a91f
patchbomb: fix up confusion between strings and lists of strings.
bos@serpentine.internal.keyresearch.com
parents:
1136
diff
changeset
|
191 prompt(prpt, default = default)).split(',') |
c3cb9f39a91f
patchbomb: fix up confusion between strings and lists of strings.
bos@serpentine.internal.keyresearch.com
parents:
1136
diff
changeset
|
192 return [a.strip() for a in addrs if a.strip()] |
c3cb9f39a91f
patchbomb: fix up confusion between strings and lists of strings.
bos@serpentine.internal.keyresearch.com
parents:
1136
diff
changeset
|
193 to = getaddrs('to', 'To') |
c3cb9f39a91f
patchbomb: fix up confusion between strings and lists of strings.
bos@serpentine.internal.keyresearch.com
parents:
1136
diff
changeset
|
194 cc = getaddrs('cc', 'Cc', '') |
875 | 195 |
196 ui.write('Finish with ^D or a dot on a line by itself.\n\n') | |
197 | |
198 body = [] | |
199 | |
200 while True: | |
201 try: l = raw_input() | |
202 except EOFError: break | |
203 if l == '.': break | |
204 body.append(l) | |
205 | |
206 msg.attach(MIMEText('\n'.join(body) + '\n')) | |
207 | |
208 ui.write('\n') | |
209 | |
1136
d451888505d7
Make diffstat optional for patchbomb script.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
1135
diff
changeset
|
210 if opts['diffstat']: |
d451888505d7
Make diffstat optional for patchbomb script.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
1135
diff
changeset
|
211 d = cdiffstat('Final summary:\n', jumbo) |
d451888505d7
Make diffstat optional for patchbomb script.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
1135
diff
changeset
|
212 if d: msg.attach(MIMEText(d)) |
875 | 213 |
214 msgs.insert(0, msg) | |
215 | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
216 if not opts['test']: |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
217 s = smtplib.SMTP() |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
218 s.connect(host = ui.config('smtp', 'host', 'mail'), |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
219 port = int(ui.config('smtp', 'port', 25))) |
1226
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
220 if ui.configbool('smtp', 'tls'): |
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
221 s.ehlo() |
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
222 s.starttls() |
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
223 s.ehlo() |
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
224 username = ui.config('smtp', 'username') |
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
225 password = ui.config('smtp', 'password') |
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
226 if username and password: |
f3837564ed03
patchbomb: add TLS and SMTP AUTH support.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1204
diff
changeset
|
227 s.login(username, password) |
875 | 228 parent = None |
229 tz = time.strftime('%z') | |
230 for m in msgs: | |
231 try: | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
232 m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) |
875 | 233 except TypeError: |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
234 m['Message-Id'] = genmsgid('patchbomb') |
875 | 235 if parent: |
236 m['In-Reply-To'] = parent | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
237 else: |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
238 parent = m['Message-Id'] |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
239 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz |
875 | 240 start_time += 1 |
241 m['From'] = sender | |
242 m['To'] = ', '.join(to) | |
243 if cc: m['Cc'] = ', '.join(cc) | |
244 ui.status('Sending ', m['Subject'], ' ...\n') | |
245 if opts['test']: | |
246 fp = os.popen(os.getenv('PAGER', 'more'), 'w') | |
247 fp.write(m.as_string(0)) | |
248 fp.write('\n') | |
249 fp.close() | |
250 else: | |
251 s.sendmail(sender, to + cc, m.as_string(0)) | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
252 if not opts['test']: |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
253 s.close() |
875 | 254 |
255 if __name__ == '__main__': | |
256 optspec = [('c', 'cc', [], 'email addresses of copy recipients'), | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
257 ('d', 'diffstat', None, 'add diffstat output to messages'), |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
258 ('f', 'from', '', 'email address of sender'), |
1604
da3f1121721b
add --plain option to patchbomb.
Vadim Gelfer <vadim.gelfer@gmail.com>
parents:
1603
diff
changeset
|
259 ('', 'plain', None, 'omit hg patch header'), |
875 | 260 ('n', 'test', None, 'print messages that would be sent'), |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
261 ('s', 'subject', '', 'subject of introductory message'), |
875 | 262 ('t', 'to', [], 'email addresses of recipients')] |
263 options = {} | |
264 try: | |
265 args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts + optspec, | |
266 options) | |
267 except fancyopts.getopt.GetoptError, inst: | |
268 u = ui.ui() | |
269 u.warn('error: %s' % inst) | |
270 sys.exit(1) | |
271 | |
272 u = ui.ui(options["verbose"], options["debug"], options["quiet"], | |
273 not options["noninteractive"]) | |
274 repo = hg.repository(ui = u) | |
275 | |
276 patchbomb(u, repo, *args, **options) |