Mercurial > hg > mercurial-crew-with-dirclash
annotate contrib/patchbomb @ 1225:ea90162e210c
Add --debugger global option
With this option, hg will drop into the Python debugger on execution.
Running 'continue' will execute normally, and the debugger will be
reinvoked if an exception is raised.
author | mpm@selenic.com |
---|---|
date | Thu, 08 Sep 2005 17:09:31 -0700 |
parents | b0f6053df539 |
children | f3837564ed03 |
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 | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
38 # |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
39 # 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
|
40 # file: |
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 # [patchbomb] |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
43 # from = My Name <my@email> |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
44 # to = recipient1, recipient2, ... |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
45 # cc = cc1, cc2, ... |
875 | 46 |
47 from email.MIMEMultipart import MIMEMultipart | |
48 from email.MIMEText import MIMEText | |
49 from mercurial import commands | |
50 from mercurial import fancyopts | |
51 from mercurial import hg | |
52 from mercurial import ui | |
53 import os | |
54 import popen2 | |
55 import smtplib | |
56 import socket | |
57 import sys | |
58 import tempfile | |
59 import time | |
60 | |
1204
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
61 try: |
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
62 # 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
|
63 # present on windows |
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
64 import readline |
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
65 except ImportError: pass |
b0f6053df539
patchbomb: continue if we can't import readline.
Bryan O'Sullivan <bos@serpentine.com>
parents:
1154
diff
changeset
|
66 |
875 | 67 def diffstat(patch): |
68 fd, name = tempfile.mkstemp() | |
69 try: | |
70 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name) | |
71 try: | |
72 for line in patch: print >> p.tochild, line | |
73 p.tochild.close() | |
74 if p.wait(): return | |
75 fp = os.fdopen(fd, 'r') | |
76 stat = [] | |
77 for line in fp: stat.append(line.lstrip()) | |
78 last = stat.pop() | |
79 stat.insert(0, last) | |
80 stat = ''.join(stat) | |
81 if stat.startswith('0 files'): raise ValueError | |
82 return stat | |
83 except: raise | |
84 finally: | |
85 try: os.unlink(name) | |
86 except: pass | |
87 | |
88 def patchbomb(ui, repo, *revs, **opts): | |
89 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
|
90 if default: prompt += ' [%s]' % default |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
91 prompt += rest |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
92 while True: |
875 | 93 r = raw_input(prompt) |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
94 if r: return r |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
95 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
|
96 if empty_ok: return r |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
97 ui.warn('Please enter a valid value.\n') |
875 | 98 |
99 def confirm(s): | |
100 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'): | |
101 raise ValueError | |
102 | |
103 def cdiffstat(summary, patch): | |
104 s = diffstat(patch) | |
105 if s: | |
106 if summary: | |
107 ui.write(summary, '\n') | |
108 ui.write(s, '\n') | |
109 confirm('Does the diffstat above look okay') | |
110 return s | |
111 | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
112 def makepatch(patch, idx, total): |
875 | 113 desc = [] |
114 node = None | |
1135
e455d91f6259
Variable 'body' was missing in patchbomb script.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
1118
diff
changeset
|
115 body = '' |
875 | 116 for line in patch: |
117 if line.startswith('#'): | |
118 if line.startswith('# Node ID'): node = line.split()[-1] | |
119 continue | |
120 if line.startswith('diff -r'): break | |
121 desc.append(line) | |
122 if not node: raise ValueError | |
1118
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
123 |
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
124 #body = ('\n'.join(desc[1:]).strip() or |
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
125 # 'Patch subject is complete summary.') |
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
126 #body += '\n\n\n' |
63b5f68d8167
patchbomb: eliminate silly complete summary message
mpm@selenic.com
parents:
1032
diff
changeset
|
127 |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
128 if opts['diffstat']: |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
129 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
|
130 body += '\n'.join(patch) |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
131 msg = MIMEText(body) |
875 | 132 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip()) |
133 if subj.endswith('.'): subj = subj[:-1] | |
134 msg['Subject'] = subj | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
135 msg['X-Mercurial-Node'] = node |
875 | 136 return msg |
137 | |
138 start_time = int(time.time()) | |
139 | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
140 def genmsgid(id): |
875 | 141 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn()) |
142 | |
143 patches = [] | |
144 | |
145 class exportee: | |
146 def __init__(self, container): | |
147 self.lines = [] | |
148 self.container = container | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
149 self.name = 'email' |
875 | 150 |
151 def write(self, data): | |
152 self.lines.append(data) | |
153 | |
154 def close(self): | |
155 self.container.append(''.join(self.lines).split('\n')) | |
156 self.lines = [] | |
157 | |
1032
706c590c9060
Get patchbomb working with tip again.
Bryan O'Sullivan <bos@serpentine.com>
parents:
998
diff
changeset
|
158 commands.export(ui, repo, *args, **{'output': exportee(patches), |
706c590c9060
Get patchbomb working with tip again.
Bryan O'Sullivan <bos@serpentine.com>
parents:
998
diff
changeset
|
159 'text': None}) |
875 | 160 |
161 jumbo = [] | |
162 msgs = [] | |
163 | |
164 ui.write('This patch series consists of %d patches.\n\n' % len(patches)) | |
165 | |
166 for p, i in zip(patches, range(len(patches))): | |
167 jumbo.extend(p) | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
168 msgs.append(makepatch(p, i + 1, len(patches))) |
875 | 169 |
170 ui.write('\nWrite the introductory message for the patch series.\n\n') | |
171 | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
172 sender = (opts['from'] or ui.config('patchbomb', 'from') or |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
173 prompt('From', ui.username())) |
875 | 174 |
175 msg = MIMEMultipart() | |
176 msg['Subject'] = '[PATCH 0 of %d] %s' % ( | |
177 len(patches), | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
178 opts['subject'] or |
875 | 179 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
|
180 |
c3cb9f39a91f
patchbomb: fix up confusion between strings and lists of strings.
bos@serpentine.internal.keyresearch.com
parents:
1136
diff
changeset
|
181 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
|
182 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
|
183 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
|
184 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
|
185 to = getaddrs('to', 'To') |
c3cb9f39a91f
patchbomb: fix up confusion between strings and lists of strings.
bos@serpentine.internal.keyresearch.com
parents:
1136
diff
changeset
|
186 cc = getaddrs('cc', 'Cc', '') |
875 | 187 |
188 ui.write('Finish with ^D or a dot on a line by itself.\n\n') | |
189 | |
190 body = [] | |
191 | |
192 while True: | |
193 try: l = raw_input() | |
194 except EOFError: break | |
195 if l == '.': break | |
196 body.append(l) | |
197 | |
198 msg.attach(MIMEText('\n'.join(body) + '\n')) | |
199 | |
200 ui.write('\n') | |
201 | |
1136
d451888505d7
Make diffstat optional for patchbomb script.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
1135
diff
changeset
|
202 if opts['diffstat']: |
d451888505d7
Make diffstat optional for patchbomb script.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
1135
diff
changeset
|
203 d = cdiffstat('Final summary:\n', jumbo) |
d451888505d7
Make diffstat optional for patchbomb script.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
1135
diff
changeset
|
204 if d: msg.attach(MIMEText(d)) |
875 | 205 |
206 msgs.insert(0, msg) | |
207 | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
208 if not opts['test']: |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
209 s = smtplib.SMTP() |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
210 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
|
211 port = int(ui.config('smtp', 'port', 25))) |
875 | 212 |
213 parent = None | |
214 tz = time.strftime('%z') | |
215 for m in msgs: | |
216 try: | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
217 m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) |
875 | 218 except TypeError: |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
219 m['Message-Id'] = genmsgid('patchbomb') |
875 | 220 if parent: |
221 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
|
222 else: |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
223 parent = m['Message-Id'] |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
224 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz |
875 | 225 start_time += 1 |
226 m['From'] = sender | |
227 m['To'] = ', '.join(to) | |
228 if cc: m['Cc'] = ', '.join(cc) | |
229 ui.status('Sending ', m['Subject'], ' ...\n') | |
230 if opts['test']: | |
231 fp = os.popen(os.getenv('PAGER', 'more'), 'w') | |
232 fp.write(m.as_string(0)) | |
233 fp.write('\n') | |
234 fp.close() | |
235 else: | |
236 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
|
237 if not opts['test']: |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
238 s.close() |
875 | 239 |
240 if __name__ == '__main__': | |
241 optspec = [('c', 'cc', [], 'email addresses of copy recipients'), | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
242 ('d', 'diffstat', None, 'add diffstat output to messages'), |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
243 ('f', 'from', '', 'email address of sender'), |
875 | 244 ('n', 'test', None, 'print messages that would be sent'), |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
245 ('s', 'subject', '', 'subject of introductory message'), |
875 | 246 ('t', 'to', [], 'email addresses of recipients')] |
247 options = {} | |
248 try: | |
249 args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts + optspec, | |
250 options) | |
251 except fancyopts.getopt.GetoptError, inst: | |
252 u = ui.ui() | |
253 u.warn('error: %s' % inst) | |
254 sys.exit(1) | |
255 | |
256 u = ui.ui(options["verbose"], options["debug"], options["quiet"], | |
257 not options["noninteractive"]) | |
258 repo = hg.repository(ui = u) | |
259 | |
260 patchbomb(u, repo, *args, **options) |